- salt/modules/postgres.py: add functions to manipulate schemas

- salt/states/postgres_schema.py: new states for schemas
- tests/unit/modules/postgres_test.py: add tests on new functions
- tests/unit/states/postgres_test.py: add tests on new functions
This commit is contained in:
Rodolphe Quiédeville 2014-09-11 12:17:18 +02:00
parent 15abeac617
commit b4dcc992c5
4 changed files with 604 additions and 0 deletions

View File

@ -1549,3 +1549,237 @@ def owner_to(dbname,
password=password,
maintenance_db=dbname)
return cmdret
# Schema related actions
def schema_create(dbname, name, owner=None,
user=None,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Creates a Postgres schema.
CLI Example:
.. code-block:: bash
salt '*' postgres.schema_create dbname name owner='owner' \\
user='user' \\
db_user='user' db_password='password'
db_host='hostname' db_port='port'
'''
# check if schema exists
if schema_exists(dbname, name,
db_user=db_user, db_password=db_password,
db_host=db_host, db_port=db_port):
log.info('{0!r} already exists in {1!r}'.format(name, dbname))
return False
sub_cmd = 'CREATE SCHEMA {0}'.format(name)
if owner is not None:
sub_cmd = '{0} AUTHORIZATION {1}'.format(sub_cmd, owner)
ret = _psql_prepare_and_run(['-c', sub_cmd],
user=db_user, password=db_password,
port=db_port, host=db_host,
maintenance_db=dbname, runas=user)
return ret['retcode'] == 0
def schema_remove(dbname, name,
user=None,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Removes a schema from the Postgres server.
CLI Example:
.. code-block:: bash
salt '*' postgres.schema_remove dbname schemaname
dbname
Database name we work on
schemaname
The schema's name we'll remove
user
System user all operations should be performed on behalf of
db_user
database username if different from config or default
db_password
user password if any password for a specified user
db_host
Database host if different from config or default
db_port
Database port if different from config or default
'''
# check if schema exists
if not schema_exists(dbname, name,
db_user=db_user, db_password=db_password,
db_host=db_host, db_port=db_port):
log.info('Schema {0!r} does not exist in {0!r}'.format(name, dbname))
return False
# schema exists, proceed
sub_cmd = 'DROP SCHEMA {0}'.format(name)
_psql_prepare_and_run(
['-c', sub_cmd],
runas=user,
maintenance_db=dbname,
host=db_host, user=db_user, port=db_port, password=db_password)
if not schema_exists(dbname, name,
db_user=db_user, db_password=db_password,
db_host=db_host, db_port=db_port):
return True
else:
log.info('Failed to delete schema {0!r}.'.format(name))
return False
def schema_exists(dbname, name,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Checks if a schema exists on the Postgres server.
CLI Example:
.. code-block:: bash
salt '*' postgres.schema_exists dbname schemaname
dbname
Database name we query on
name
Schema name we look for
db_user
database username if different from config or default
db_password
user password if any password for a specified user
db_host
Database host if different from config or default
db_port
Database port if different from config or default
'''
return bool(
schema_get(dbname, name,
db_user=db_user,
db_host=db_host,
db_port=db_port,
db_password=db_password))
def schema_get(dbname, name,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Return a dict with information about schemas in a database.
CLI Example:
.. code-block:: bash
salt '*' postgres.schema_get dbname name
dbname
Database name we query on
name
Schema name we look for
db_user
database username if different from config or default
db_password
user password if any password for a specified user
db_host
Database host if different from config or default
db_port
Database port if different from config or default
'''
all_schemas = schema_list(dbname,
db_user=db_user,
db_host=db_host,
db_port=db_port,
db_password=db_password)
try:
return all_schemas.get(name, None)
except AttributeError:
log.error('Could not retrieve Postgres schema. Is Postgres running?')
return False
def schema_list(dbname,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Return a dict with information about schemas in a Postgres database.
CLI Example:
.. code-block:: bash
salt '*' postgres.schema_list dbname
dbname
Database name we query on
db_user
database username if different from config or default
db_password
user password if any password for a specified user
db_host
Database host if different from config or default
db_port
Database port if different from config or default
'''
ret = {}
query = (''.join([
'SELECT '
'pg_namespace.nspname as "name",'
'pg_namespace.nspacl as "acl", '
'pg_roles.rolname as "owner" '
'FROM pg_namespace '
'LEFT JOIN pg_roles ON pg_roles.oid = pg_namespace.nspowner '
]))
rows = psql_query(query,
host=db_host,
user=db_user,
port=db_port,
maintenance_db=dbname,
password=db_password)
for row in rows:
retrow = {}
for key in ('owner', 'acl'):
retrow[key] = row[key]
ret[row['name']] = retrow
return ret

View File

@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
'''
Management of PostgreSQL schemas
================================
The postgres_schemas module is used to create and manage Postgres schemas.
.. code-block:: yaml
public:
postgres_schema.present 'dbname' 'name'
'''
# Import Python libs
# Import salt libs
import salt.utils
import logging
# Salt imports
from salt.modules import postgres
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load if the postgres module is present
'''
return 'postgres.schema_exists' in __salt__
def present(dbname, name,
owner=None,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Ensure that the named schema is present in the database.
dbname
The database's name will work on
name
The name of the schema to manage
db_user
database username if different from config or default
db_password
user password if any password for a specified user
db_host
Database host if different from config or default
db_port
Database port if different from config or default
'''
ret = {'dbname': dbname,
'name': name,
'changes': {},
'result': True,
'comment': 'Schema {0} is already present in '
'database {1}'.format(name, dbname)}
db_args = {
'db_user': db_user,
'db_password': db_password,
'db_host': db_host,
'db_port': db_port
}
# check if schema exists
schema_attr = __salt__['postgres.schema_get'](dbname, name, **db_args)
cret = None
# The schema is not present, make it!
if schema_attr is None:
cret = __salt__['postgres.schema_create'](dbname,
name,
owner=owner,
**db_args)
else:
msg = 'Schema {0} already exists in database {1}'
cret = None
if cret:
msg = 'Schema {0} has been created in database {1}'
ret['result'] = True
ret['changes'][name] = 'Present'
elif cret is not None:
msg = 'Failed to create schema {0} in database {1}'
ret['result'] = False
else:
msg = 'Schema {0} already exists in database {1}'
ret['result'] = True
ret['comment'] = msg.format(name, dbname)
return ret
def absent(dbname, name,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Ensure that the named schema is absent
dbname
The database's name will work on
name
The name of the schema to remove
db_user
database username if different from config or default
db_password
user password if any password for a specified user
db_host
Database host if different from config or default
db_port
Database port if different from config or default
'''
ret = {'name': name,
'dbname': dbname,
'changes': {},
'result': True,
'comment': ''}
db_args = {
'db_user': db_user,
'db_password': db_password,
'db_host': db_host,
'db_port': db_port
}
# check if schema exists and remove it
if __salt__['postgres.schema_exists'](dbname, name, **db_args):
if __salt__['postgres.schema_remove'](dbname, name, **db_args):
ret['comment'] = 'Schema {0} has been removed' \
' from database {1}'.format(name, dbname)
ret['changes'][name] = 'Absent'
return ret
else:
ret['result'] = False
ret['comment'] = 'Schema {0} failed to be removed'.format(name)
return ret
else:
ret['comment'] = 'Schema {0} is not present in database {1},' \
' so it cannot be removed'.format(name, dbname)
return ret

View File

@ -27,6 +27,13 @@ test_list_db_csv = (
'test_db,postgres,LATIN1,en_US,en_US,,pg_default'
)
test_list_schema_csv = (
'name,owner,acl\n'
'public,postgres,"{postgres=UC/postgres,=UC/postgres}"\n'
'pg_toast,postgres,""'
)
if NO_MOCK is False:
SALT_STUB = {
'config.option': Mock(),
@ -666,6 +673,136 @@ class PostgresTestCase(TestCase):
'foo', 'bar', True),
'md596948aad3fcae80c08a35c9b5958cd89')
@patch('salt.modules.postgres._run_psql',
Mock(return_value={'retcode': None,
'stdout': test_list_schema_csv}))
def test_schema_list(self):
ret = postgres.schema_list(
'maint_db',
db_user='testuser',
db_host='testhost',
db_port='testport',
db_password='foo'
)
self.assertDictEqual(ret, {
'public': {'acl': '{postgres=UC/postgres,=UC/postgres}',
'owner': 'postgres'},
'pg_toast': {'acl': '', 'owner': 'postgres'}
})
@patch('salt.modules.postgres._run_psql',
Mock(return_value={'retcode': None}))
@patch('salt.modules.postgres.psql_query',
Mock(return_value=[
{
'name': 'public',
'acl': '{postgres=UC/postgres,=UC/postgres}',
'owner': 'postgres'
}]))
def test_schema_exists(self):
ret = postgres.schema_exists(
'template1',
'public'
)
self.assertTrue(ret)
@patch('salt.modules.postgres._run_psql',
Mock(return_value={'retcode': None}))
@patch('salt.modules.postgres.psql_query',
Mock(return_value=[
{
'name': 'public',
'acl': '{postgres=UC/postgres,=UC/postgres}',
'owner': 'postgres'
}]))
def test_schema_get(self):
ret = postgres.schema_get(
'template1',
'public'
)
self.assertTrue(ret)
@patch('salt.modules.postgres._run_psql',
Mock(return_value={'retcode': None}))
@patch('salt.modules.postgres.psql_query',
Mock(return_value=[
{
'name': 'public',
'acl': '{postgres=UC/postgres,=UC/postgres}',
'owner': 'postgres'
}]))
def test_schema_get(self):
ret = postgres.schema_get(
'template1',
'pg_toast'
)
self.assertFalse(ret)
@patch('salt.modules.postgres._run_psql',
Mock(return_value={'retcode': None}))
@patch('salt.modules.postgres.schema_exists', Mock(return_value=False))
def test_schema_create(self):
postgres.schema_create(
'test_db',
'test_schema',
user='user',
db_host='test_host',
db_port='test_port',
db_user='test_user',
db_password='test_password'
)
postgres._run_psql.assert_called_once_with(
"/usr/bin/pgsql --no-align --no-readline --no-password "
"--username test_user "
"--host test_host --port test_port "
"--dbname test_db -c 'CREATE SCHEMA test_schema'",
host='test_host', port='test_port',
password='test_password', user='test_user', runas='user')
@patch('salt.modules.postgres.schema_exists', Mock(return_value=True))
def test_schema_create2(self):
ret = postgres.schema_create('test_db',
'test_schema',
user='user',
db_host='test_host',
db_port='test_port',
db_user='test_user',
db_password='test_password'
)
self.assertFalse(ret)
@patch('salt.modules.postgres._run_psql',
Mock(return_value={'retcode': None}))
@patch('salt.modules.postgres.schema_exists', Mock(return_value=True))
def test_schema_remove(self):
postgres.schema_remove(
'test_db',
'test_schema',
user='user',
db_host='test_host',
db_port='test_port',
db_user='test_user',
db_password='test_password'
)
postgres._run_psql.assert_called_once_with(
"/usr/bin/pgsql --no-align --no-readline --no-password "
"--username test_user "
"--host test_host --port test_port "
"--dbname test_db -c 'DROP SCHEMA test_schema'",
host='test_host', port='test_port',
password='test_password', user='test_user', runas='user')
@patch('salt.modules.postgres.schema_exists', Mock(return_value=False))
def test_schema_remove2(self):
ret = postgres.schema_remove('test_db',
'test_schema',
user='user',
db_host='test_host',
db_port='test_port',
db_user='test_user',
db_password='test_password'
)
self.assertFalse(ret)
if __name__ == '__main__':
from integration import run_tests

View File

@ -16,12 +16,14 @@ from salt.states import (
postgres_user,
postgres_group,
postgres_extension,
postgres_schema,
)
MODS = (
postgres_database,
postgres_user,
postgres_group,
postgres_extension,
postgres_schema,
)
@ -483,6 +485,82 @@ class PostgresExtensionTestCase(TestCase):
)
@skipIf(NO_MOCK, NO_MOCK_REASON)
@patch.multiple(postgres_schema,
__grains__={'os_family': 'Linux'},
__salt__=SALT_STUB)
@patch('salt.utils.which', Mock(return_value='/usr/bin/pgsql'))
class PostgresSchemaTestCase(TestCase):
@patch.dict(SALT_STUB, {
'postgres.schema_get': Mock(return_value=None),
'postgres.schema_create': MagicMock(),
})
def test_present_creation(self):
ret = postgres_schema.present('dbname', 'foo')
self.assertEqual(
ret,
{'comment': 'Schema foo has been created in database dbname',
'changes': {'foo': 'Present'},
'dbname': 'dbname',
'name': 'foo',
'result': True}
)
self.assertEqual(SALT_STUB['postgres.schema_create'].call_count, 1)
@patch.dict(SALT_STUB, {
'postgres.schema_get': Mock(return_value={'foo':
{'acl': '',
'owner': 'postgres'}
}),
'postgres.schema_create': MagicMock(),
})
def test_present_nocreation(self):
ret = postgres_schema.present('dbname', 'foo')
self.assertEqual(
ret,
{'comment': 'Schema foo already exists in database dbname',
'changes': {},
'dbname': 'dbname',
'name': 'foo',
'result': True}
)
self.assertEqual(SALT_STUB['postgres.schema_create'].call_count, 0)
@patch.dict(SALT_STUB, {
'postgres.schema_exists': Mock(return_value=True),
'postgres.schema_remove': MagicMock(),
})
def test_absent_remove(self):
ret = postgres_schema.absent('dbname', 'foo')
self.assertEqual(
ret,
{'comment': 'Schema foo has been removed from database dbname',
'changes': {'foo': 'Absent'},
'dbname': 'dbname',
'name': 'foo',
'result': True}
)
self.assertEqual(SALT_STUB['postgres.schema_remove'].call_count, 1)
@patch.dict(SALT_STUB, {
'postgres.schema_exists': Mock(return_value=False),
'postgres.schema_remove': MagicMock(),
})
def test_absent_noremove(self):
ret = postgres_schema.absent('dbname', 'foo')
self.assertEqual(
ret,
{'comment': 'Schema foo is not present in database dbname,'
' so it cannot be removed',
'changes': {},
'dbname': 'dbname',
'name': 'foo',
'result': True}
)
self.assertEqual(SALT_STUB['postgres.schema_remove'].call_count, 0)
if __name__ == '__main__':
from integration import run_tests
run_tests(PostgresExtensionTestCase, needs_daemon=False)