mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge pull request #9831 from makinacorpus/pgsql
[need review] PGSQL state/mod improvments
This commit is contained in:
commit
6b09f5b8fd
@ -69,6 +69,7 @@ Full list of builtin state modules
|
||||
postgres_database
|
||||
postgres_group
|
||||
postgres_user
|
||||
postgres_extension
|
||||
powerpath
|
||||
process
|
||||
quota
|
||||
|
6
doc/ref/states/all/salt.states.postgres_extension.rst
Normal file
6
doc/ref/states/all/salt.states.postgres_extension.rst
Normal file
@ -0,0 +1,6 @@
|
||||
==============================
|
||||
salt.states.postgres_extension
|
||||
==============================
|
||||
|
||||
.. automodule:: salt.states.postgres_extension
|
||||
:members:
|
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,12 @@ def present(name,
|
||||
owner=None,
|
||||
template=None,
|
||||
runas=None,
|
||||
user=None):
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named database is present with the specified properties.
|
||||
For more information about all of these options see man createdb(1)
|
||||
@ -65,6 +70,18 @@ def present(name,
|
||||
user
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
db_user
|
||||
database username if different from config or defaul
|
||||
|
||||
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
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
'''
|
||||
ret = {'name': name,
|
||||
@ -96,19 +113,30 @@ def present(name,
|
||||
user = runas
|
||||
runas = None
|
||||
|
||||
dbs = __salt__['postgres.db_list'](runas=user)
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
dbs = __salt__['postgres.db_list'](**db_args)
|
||||
db_params = dbs.get(name, {})
|
||||
|
||||
if name in dbs and all((
|
||||
db_params.get('Tablespace') == tablespace if tablespace else True,
|
||||
db_params.get('Encoding') == encoding if encoding else True,
|
||||
(
|
||||
db_params.get('Encoding').lower() == encoding.lower()
|
||||
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('Encoding').lower() != encoding.lower() 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
|
||||
)):
|
||||
@ -126,20 +154,25 @@ def present(name,
|
||||
ret['comment'] = 'Database {0} exists, but parameters ' \
|
||||
'need to be changed'.format(name)
|
||||
return ret
|
||||
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=user):
|
||||
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,
|
||||
**db_args)
|
||||
):
|
||||
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):
|
||||
elif (
|
||||
name in dbs and __salt__['postgres.db_alter'](
|
||||
name,
|
||||
tablespace=tablespace,
|
||||
owner=owner, **db_args)
|
||||
):
|
||||
ret['comment'] = ('Parameters for database {0} have been changed'
|
||||
).format(name)
|
||||
ret['changes'][name] = 'Parameters changed'
|
||||
@ -154,13 +187,32 @@ def present(name,
|
||||
return ret
|
||||
|
||||
|
||||
def absent(name, runas=None, user=None):
|
||||
def absent(name,
|
||||
runas=None,
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named database is absent
|
||||
|
||||
name
|
||||
The name of the database to remove
|
||||
|
||||
db_user
|
||||
database username if different from config or defaul
|
||||
|
||||
password
|
||||
user password if any password for a specified user
|
||||
|
||||
host
|
||||
Database host if different from config or default
|
||||
|
||||
port
|
||||
Database port if different from config or default
|
||||
|
||||
runas
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
@ -199,13 +251,21 @@ def absent(name, runas=None, user=None):
|
||||
user = runas
|
||||
runas = None
|
||||
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
#check if db exists and remove it
|
||||
if __salt__['postgres.db_exists'](name, runas=user):
|
||||
if __salt__['postgres.db_exists'](name, **db_args):
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Database {0} is set to be removed'.format(name)
|
||||
return ret
|
||||
if __salt__['postgres.db_remove'](name, runas=user):
|
||||
if __salt__['postgres.db_remove'](name, **db_args):
|
||||
ret['comment'] = 'Database {0} has been removed'.format(name)
|
||||
ret['changes'][name] = 'Absent'
|
||||
return ret
|
||||
|
218
salt/states/postgres_extension.py
Normal file
218
salt/states/postgres_extension.py
Normal file
@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Management of PostgreSQL extensions (eg: postgis)
|
||||
=================================================
|
||||
|
||||
The postgres_users module is used to create and manage Postgres extensions.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
adminpack:
|
||||
postgres_extension.present
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import hashlib
|
||||
|
||||
# Import salt libs
|
||||
import logging
|
||||
import salt.utils
|
||||
|
||||
from salt.modules import postgres
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if the postgres module is present
|
||||
'''
|
||||
return 'postgres_extension' if (
|
||||
'postgres.create_extension' in __salt__
|
||||
) else False
|
||||
|
||||
|
||||
def present(name,
|
||||
if_not_exists=None,
|
||||
schema=None,
|
||||
ext_version=None,
|
||||
from_version=None,
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named user is present with the specified privileges
|
||||
|
||||
name
|
||||
The name of the extension to manage
|
||||
|
||||
if_not_exists
|
||||
Add a if_not_exists switch to the ddl statement
|
||||
|
||||
schema
|
||||
Schema to install the extension into
|
||||
|
||||
from_version
|
||||
Old extension version if already installed
|
||||
|
||||
ext_version
|
||||
version to install
|
||||
|
||||
user
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
maintenance_db
|
||||
Database to act 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 = {'name': name,
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': 'Extention {0} is already present'.format(name)}
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
# check if user exists
|
||||
mode = 'create'
|
||||
mtdata = __salt__['postgres.create_metadata'](
|
||||
name,
|
||||
schema=schema,
|
||||
ext_version=ext_version,
|
||||
from_version=from_version, **db_args)
|
||||
|
||||
# The user is not present, make it!
|
||||
toinstall = postgres._EXTENSION_NOT_INSTALLED in mtdata
|
||||
if toinstall:
|
||||
mode = 'install'
|
||||
toupgrade = False
|
||||
if postgres._EXTENSION_INSTALLED in mtdata:
|
||||
for flag in [
|
||||
postgres._EXTENSION_TO_MOVE,
|
||||
postgres._EXTENSION_TO_UPGRADE
|
||||
]:
|
||||
if flag in mtdata:
|
||||
toupgrade = True
|
||||
mode = 'upgrade'
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
if mode:
|
||||
ret['comment'] = 'Extension {0} is set to be {1}ed'.format(
|
||||
name, mode).replace('eed', 'ed')
|
||||
return ret
|
||||
cret = None
|
||||
if toinstall or toupgrade:
|
||||
cret = __salt__['postgres.create_extension'.format(mode)](
|
||||
name=name,
|
||||
if_not_exists=if_not_exists,
|
||||
schema=schema,
|
||||
ext_version=ext_version,
|
||||
from_version=from_version,
|
||||
**db_args)
|
||||
if cret:
|
||||
ret['comment'] = 'The extension {0} has been {1}ed'.format(name, mode)
|
||||
elif cret is not None:
|
||||
ret['comment'] = 'Failed to {1} extension {0}'.format(name, mode)
|
||||
ret['result'] = False
|
||||
else:
|
||||
ret['result'] = True
|
||||
return ret
|
||||
|
||||
|
||||
def absent(name,
|
||||
if_exists=None,
|
||||
restrict=None,
|
||||
cascade=None,
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named user is absent
|
||||
|
||||
name
|
||||
Extension username of the extension to remove
|
||||
|
||||
cascade
|
||||
Drop on cascade
|
||||
|
||||
if_exists
|
||||
Add if exist slug
|
||||
|
||||
restrict
|
||||
Add restrict slug
|
||||
|
||||
maintenance_db
|
||||
Database to act on
|
||||
|
||||
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
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
# check if user exists and remove it
|
||||
exists = __salt__['postgres.is_installed_extension'](name, **db_args)
|
||||
if exists:
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Extension {0} is set to be removed'.format(name)
|
||||
return ret
|
||||
if __salt__['postgres.drop_extension'](name,
|
||||
if_exists=if_exists,
|
||||
restrict=restrict,
|
||||
cascade=cascade,
|
||||
**db_args):
|
||||
ret['comment'] = 'Extension {0} has been removed'.format(name)
|
||||
ret['changes'][name] = 'Absent'
|
||||
return ret
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Extension {0} failed to be removed'.format(name)
|
||||
return ret
|
||||
else:
|
||||
ret['comment'] = 'Extension {0} is not present, so it cannot ' \
|
||||
'be removed'.format(name)
|
||||
|
||||
return ret
|
@ -11,27 +11,44 @@ The postgres_group module is used to create and manage Postgres groups.
|
||||
postgres_group.present
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import hashlib
|
||||
|
||||
# Import salt libs
|
||||
import salt.utils
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if the postgres module is present
|
||||
'''
|
||||
return 'postgres_group' if 'postgres.user_exists' in __salt__ else False
|
||||
return 'postgres_group' if (
|
||||
'postgres.group_create' in __salt__
|
||||
) else False
|
||||
|
||||
|
||||
def present(name,
|
||||
createdb=False,
|
||||
createuser=False,
|
||||
encrypted=False,
|
||||
superuser=False,
|
||||
replication=False,
|
||||
createdb=None,
|
||||
createroles=None,
|
||||
createuser=None,
|
||||
encrypted=None,
|
||||
superuser=None,
|
||||
inherit=None,
|
||||
login=None,
|
||||
replication=None,
|
||||
password=None,
|
||||
groups=None,
|
||||
runas=None,
|
||||
user=None):
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named group is present with the specified privileges
|
||||
|
||||
@ -41,12 +58,22 @@ def present(name,
|
||||
createdb
|
||||
Is the group allowed to create databases?
|
||||
|
||||
createroles
|
||||
Is the group allowed to create other roles/users
|
||||
|
||||
createuser
|
||||
Is the group allowed to create other users?
|
||||
Alias to create roles, and history problem, in pgsql normally
|
||||
createuser == superuser
|
||||
|
||||
encrypted
|
||||
Should the password be encrypted in the system catalog?
|
||||
|
||||
login
|
||||
Should the group have login perm
|
||||
|
||||
inherit
|
||||
Should the group inherit permissions
|
||||
|
||||
superuser
|
||||
Should the new group be a "superuser"
|
||||
|
||||
@ -68,6 +95,18 @@ def present(name,
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
db_user
|
||||
database username if different from config or defaul
|
||||
|
||||
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,
|
||||
'changes': {},
|
||||
@ -80,6 +119,8 @@ def present(name,
|
||||
'added in 0.17.0',
|
||||
_dont_call_warnings=True
|
||||
)
|
||||
if createuser:
|
||||
createroles = True
|
||||
if runas:
|
||||
# Warn users about the deprecation
|
||||
ret.setdefault('warnings', []).append(
|
||||
@ -98,34 +139,99 @@ def present(name,
|
||||
user = runas
|
||||
runas = None
|
||||
|
||||
# check if user exists
|
||||
if __salt__['postgres.user_exists'](name, runas=user):
|
||||
return ret
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
|
||||
# check if group exists
|
||||
mode = 'create'
|
||||
group_attr = __salt__['postgres.role_get'](name, **db_args)
|
||||
if group_attr is not None:
|
||||
mode = 'update'
|
||||
|
||||
# The user is not present, make it!
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Group {0} is set to be created'.format(name)
|
||||
ret['comment'] = 'Group {0} is set to be {1}d'.format(name, mode)
|
||||
return ret
|
||||
if __salt__['postgres.group_create'](groupname=name,
|
||||
createdb=createdb,
|
||||
createuser=createuser,
|
||||
encrypted=encrypted,
|
||||
superuser=superuser,
|
||||
replication=replication,
|
||||
rolepassword=password,
|
||||
groups=groups,
|
||||
runas=user):
|
||||
ret['comment'] = 'The group {0} has been created'.format(name)
|
||||
ret['changes'][name] = 'Present'
|
||||
cret = None
|
||||
update = {}
|
||||
if mode == 'update':
|
||||
if (
|
||||
createdb is not None
|
||||
and group_attr['can create databases'] != createdb
|
||||
):
|
||||
update['createdb'] = createdb
|
||||
if (
|
||||
inherit is not None
|
||||
and group_attr['inherits privileges'] != inherit
|
||||
):
|
||||
update['inherit'] = inherit
|
||||
if (login is not None and group_attr['can login'] != login):
|
||||
update['createdb'] = createdb
|
||||
if (
|
||||
createroles is not None
|
||||
and group_attr['can create roles'] != createroles
|
||||
):
|
||||
update['createroles'] = createroles
|
||||
if (
|
||||
replication is not None
|
||||
and group_attr['replication'] != replication
|
||||
):
|
||||
update['replication'] = replication
|
||||
if (superuser is not None and group_attr['superuser'] != superuser):
|
||||
update['superuser'] = superuser
|
||||
if (
|
||||
password is not None
|
||||
and group_attr['password'] != "md5{0}".format(
|
||||
hashlib.md5('{0}{1}'.format(password, name)).hexdigest())
|
||||
):
|
||||
log.info('MD5 hash of the password of user {0} '
|
||||
'is not what was expected. However, '
|
||||
'Please note that postgres.user_exists '
|
||||
'only supports MD5 hashed passwords'.format(name))
|
||||
update['password'] = True
|
||||
if (mode == 'create' or (mode == 'update' and update)):
|
||||
cret = __salt__['postgres.group_{0}'.format(mode)](
|
||||
groupname=name,
|
||||
createdb=createdb,
|
||||
createroles=createroles,
|
||||
encrypted=encrypted,
|
||||
login=login,
|
||||
inherit=inherit,
|
||||
superuser=superuser,
|
||||
replication=replication,
|
||||
rolepassword=password,
|
||||
groups=groups,
|
||||
**db_args)
|
||||
else:
|
||||
cret = None
|
||||
if cret:
|
||||
ret['comment'] = 'The group {0} has been {1}d'.format(name, mode)
|
||||
if update:
|
||||
ret['changes'][name] = update
|
||||
elif cret is not None:
|
||||
ret['comment'] = 'Failed to create group {0}'.format(name)
|
||||
ret['result'] = False
|
||||
else:
|
||||
ret['result'] = True
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def absent(name, runas=None, user=None):
|
||||
def absent(name,
|
||||
runas=None,
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named group is absent
|
||||
|
||||
@ -141,6 +247,18 @@ def absent(name, runas=None, user=None):
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
db_user
|
||||
database username if different from config or defaul
|
||||
|
||||
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,
|
||||
'changes': {},
|
||||
@ -171,13 +289,21 @@ def absent(name, runas=None, user=None):
|
||||
user = runas
|
||||
runas = None
|
||||
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
# check if group exists and remove it
|
||||
if __salt__['postgres.user_exists'](name, runas=user):
|
||||
if __salt__['postgres.user_exists'](name, **db_args):
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Group {0} is set to be removed'.format(name)
|
||||
return ret
|
||||
if __salt__['postgres.group_remove'](name, runas=user):
|
||||
if __salt__['postgres.group_remove'](name, **db_args):
|
||||
ret['comment'] = 'Group {0} has been removed'.format(name)
|
||||
ret['changes'][name] = 'Absent'
|
||||
return ret
|
||||
|
@ -11,27 +11,43 @@ The postgres_users module is used to create and manage Postgres users.
|
||||
postgres_user.present
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import hashlib
|
||||
|
||||
# Import salt libs
|
||||
import salt.utils
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if the postgres module is present
|
||||
'''
|
||||
return 'postgres_user' if 'postgres.user_exists' in __salt__ else False
|
||||
return 'postgres_user' if (
|
||||
'postgres.user_exists' in __salt__
|
||||
) else False
|
||||
|
||||
|
||||
def present(name,
|
||||
createdb=None,
|
||||
createroles=None,
|
||||
createuser=None,
|
||||
encrypted=None,
|
||||
superuser=None,
|
||||
replication=None,
|
||||
inherit=None,
|
||||
login=None,
|
||||
password=None,
|
||||
groups=None,
|
||||
runas=None,
|
||||
user=None):
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named user is present with the specified privileges
|
||||
|
||||
@ -41,12 +57,21 @@ def present(name,
|
||||
createdb
|
||||
Is the user allowed to create databases?
|
||||
|
||||
createuser
|
||||
createroles
|
||||
Is the user allowed to create other users?
|
||||
|
||||
createuser
|
||||
Alias to create roles
|
||||
|
||||
encrypted
|
||||
Should the password be encrypted in the system catalog?
|
||||
|
||||
login
|
||||
Should the group have login perm
|
||||
|
||||
inherit
|
||||
Should the group inherit permissions
|
||||
|
||||
superuser
|
||||
Should the new user be a "superuser"
|
||||
|
||||
@ -68,6 +93,18 @@ def present(name,
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
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,
|
||||
'changes': {},
|
||||
@ -80,6 +117,8 @@ def present(name,
|
||||
'added in 0.17.0',
|
||||
_dont_call_warnings=True
|
||||
)
|
||||
if createuser:
|
||||
createroles = True
|
||||
if runas:
|
||||
# Warn users about the deprecation
|
||||
ret.setdefault('warnings', []).append(
|
||||
@ -98,69 +137,99 @@ def present(name,
|
||||
user = runas
|
||||
runas = None
|
||||
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
|
||||
# check if user exists
|
||||
if __salt__['postgres.user_exists'](name,
|
||||
createdb=createdb,
|
||||
createuser=createuser,
|
||||
superuser=superuser,
|
||||
replication=replication,
|
||||
rolepassword=password,
|
||||
runas=user):
|
||||
return ret
|
||||
# User might exist with different password or attributes
|
||||
if __salt__['postgres.user_exists'](name, runas=user):
|
||||
# User does exist with different password or attributes
|
||||
# Lets update it
|
||||
ret['changes']['Updated user "{0}" successfully'.format(name)] = \
|
||||
__salt__['postgres.user_update'](name,
|
||||
createdb=createdb,
|
||||
createuser=createuser,
|
||||
encrypted=encrypted,
|
||||
superuser=superuser,
|
||||
replication=replication,
|
||||
rolepassword=password,
|
||||
groups=groups,
|
||||
runas=runas,
|
||||
user=user)
|
||||
return ret
|
||||
mode = 'create'
|
||||
user_attr = __salt__['postgres.role_get'](name, **db_args)
|
||||
if user_attr is not None:
|
||||
mode = 'update'
|
||||
|
||||
# The user is not present, make it!
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'User {0} is set to be created'.format(name)
|
||||
ret['comment'] = 'User {0} is set to be {1}d'.format(name, mode)
|
||||
return ret
|
||||
|
||||
# Setting default values
|
||||
if createdb is None:
|
||||
createdb = False
|
||||
if createuser is None:
|
||||
createuser = False
|
||||
if encrypted is None:
|
||||
encrypted = False
|
||||
if superuser is None:
|
||||
superuser = False
|
||||
if replication is None:
|
||||
replication = False
|
||||
|
||||
if __salt__['postgres.user_create'](username=name,
|
||||
createdb=createdb,
|
||||
createuser=createuser,
|
||||
encrypted=encrypted,
|
||||
superuser=superuser,
|
||||
replication=replication,
|
||||
rolepassword=password,
|
||||
groups=groups,
|
||||
runas=user):
|
||||
ret['comment'] = 'The user {0} has been created'.format(name)
|
||||
ret['changes'][name] = 'Present'
|
||||
cret = None
|
||||
update = {}
|
||||
if mode == 'update':
|
||||
if (
|
||||
createdb is not None
|
||||
and user_attr['can create databases'] != createdb
|
||||
):
|
||||
update['createdb'] = createdb
|
||||
if (
|
||||
inherit is not None
|
||||
and user_attr['inherits privileges'] != inherit
|
||||
):
|
||||
update['inherit'] = inherit
|
||||
if (login is not None and user_attr['can login'] != login):
|
||||
update['createdb'] = createdb
|
||||
if (
|
||||
createroles is not None
|
||||
and user_attr['can create roles'] != createroles
|
||||
):
|
||||
update['createroles'] = createroles
|
||||
if (
|
||||
replication is not None
|
||||
and user_attr['replication'] != replication
|
||||
):
|
||||
update['replication'] = replication
|
||||
if (superuser is not None and user_attr['superuser'] != superuser):
|
||||
update['superuser'] = superuser
|
||||
if (
|
||||
password is not None
|
||||
and user_attr['password'] != "md5{0}".format(
|
||||
hashlib.md5('{0}{1}'.format(password, name)).hexdigest())
|
||||
):
|
||||
log.info('MD5 hash of the password of user {0} '
|
||||
'is not what was expected. However, '
|
||||
'Please note that postgres.user_exists '
|
||||
'only supports MD5 hashed passwords'.format(name))
|
||||
update['password'] = True
|
||||
if (mode == 'create' or (mode == 'update' and update)):
|
||||
cret = __salt__['postgres.user_{0}'.format(mode)](
|
||||
username=name,
|
||||
createdb=createdb,
|
||||
createroles=createroles,
|
||||
encrypted=encrypted,
|
||||
superuser=superuser,
|
||||
login=login,
|
||||
inherit=inherit,
|
||||
replication=replication,
|
||||
rolepassword=password,
|
||||
groups=groups,
|
||||
**db_args)
|
||||
else:
|
||||
cret = None
|
||||
if cret:
|
||||
ret['comment'] = 'The user {0} has been {1}d'.format(name, mode)
|
||||
if update:
|
||||
ret['changes'][name] = update
|
||||
elif cret is not None:
|
||||
ret['comment'] = 'Failed to create user {0}'.format(name)
|
||||
ret['result'] = False
|
||||
else:
|
||||
ret['result'] = True
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def absent(name, runas=None, user=None):
|
||||
def absent(name,
|
||||
runas=None,
|
||||
user=None,
|
||||
maintenance_db=None,
|
||||
db_password=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
db_user=None):
|
||||
'''
|
||||
Ensure that the named user is absent
|
||||
|
||||
@ -176,6 +245,18 @@ def absent(name, runas=None, user=None):
|
||||
System user all operations should be performed on behalf of
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
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,
|
||||
'changes': {},
|
||||
@ -206,13 +287,21 @@ def absent(name, runas=None, user=None):
|
||||
user = runas
|
||||
runas = None
|
||||
|
||||
db_args = {
|
||||
'maintenance_db': maintenance_db,
|
||||
'runas': user,
|
||||
'host': db_host,
|
||||
'user': db_user,
|
||||
'port': db_port,
|
||||
'password': db_password,
|
||||
}
|
||||
# check if user exists and remove it
|
||||
if __salt__['postgres.user_exists'](name, runas=user):
|
||||
if __salt__['postgres.user_exists'](name, **db_args):
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'User {0} is set to be removed'.format(name)
|
||||
return ret
|
||||
if __salt__['postgres.user_remove'](name, runas=user):
|
||||
if __salt__['postgres.user_remove'](name, **db_args):
|
||||
ret['comment'] = 'User {0} has been removed'.format(name)
|
||||
ret['changes'][name] = 'Absent'
|
||||
return ret
|
||||
|
@ -14,11 +14,15 @@ from salt.modules import postgres
|
||||
postgres.__grains__ = None # in order to stub it w/patch below
|
||||
postgres.__salt__ = None # in order to stub it w/patch below
|
||||
|
||||
test_list_db_csv = 'Name,Owner,Encoding,Collate,Ctype,Access privileges,Tablespace\n\
|
||||
template1,postgres,LATIN1,en_US,en_US,"{=c/postgres,postgres=CTc/postgres}",pg_default\n\
|
||||
template0,postgres,LATIN1,en_US,en_US,"{=c/postgres,postgres=CTc/postgres}",pg_default\n\
|
||||
postgres,postgres,LATIN1,en_US,en_US,,pg_default\n\
|
||||
test_db,postgres,LATIN1,en_US,en_US,,pg_default'
|
||||
test_list_db_csv = (
|
||||
'Name,Owner,Encoding,Collate,Ctype,Access privileges,Tablespace\n'
|
||||
'template1,postgres,LATIN1,en_US,en_US'
|
||||
',"{=c/postgres,postgres=CTc/postgres}",pg_default\n'
|
||||
'template0,postgres,LATIN1,en_US,en_US'
|
||||
',"{=c/postgres,postgres=CTc/postgres}",pg_default\n'
|
||||
'postgres,postgres,LATIN1,en_US,en_US,,pg_default\n'
|
||||
'test_db,postgres,LATIN1,en_US,en_US,,pg_default'
|
||||
)
|
||||
|
||||
if NO_MOCK is False:
|
||||
SALT_STUB = {
|
||||
@ -34,8 +38,7 @@ else:
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
@patch.multiple(postgres,
|
||||
__grains__={'os_family': 'Linux'},
|
||||
__salt__=SALT_STUB,
|
||||
)
|
||||
__salt__=SALT_STUB)
|
||||
@patch('salt.utils.which', Mock(return_value='/usr/bin/pgsql'))
|
||||
class PostgresTestCase(TestCase):
|
||||
def test_run_psql(self):
|
||||
@ -44,7 +47,8 @@ class PostgresTestCase(TestCase):
|
||||
|
||||
self.assertEqual('postgres', cmd.call_args[1]['runas'])
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
def test_db_alter(self):
|
||||
postgres.db_alter('dbname',
|
||||
user='testuser',
|
||||
@ -54,13 +58,15 @@ class PostgresTestCase(TestCase):
|
||||
password='foo',
|
||||
tablespace='testspace',
|
||||
owner='otheruser',
|
||||
runas='foo'
|
||||
)
|
||||
runas='foo')
|
||||
postgres._run_psql.assert_called_once_with(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_db -c \'ALTER DATABASE "dbname" OWNER TO "otheruser"\'',
|
||||
host='testhost', password='foo', runas='foo')
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser '
|
||||
'--host testhost --port testport --dbname maint_db '
|
||||
'-c \'ALTER DATABASE "dbname" OWNER TO "otheruser"\'',
|
||||
host='testhost', password='foo', runas='foo', port='testport')
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
def test_db_create(self):
|
||||
postgres.db_create(
|
||||
'dbname',
|
||||
@ -74,11 +80,15 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
postgres._run_psql.assert_called_once_with(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_db -c \'CREATE DATABASE "dbname" WITH TABLESPACE = testspace OWNER = "otheruser"\'',
|
||||
host='testhost', password='foo', runas='foo')
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser '
|
||||
'--host testhost --port testport --dbname maint_db -c '
|
||||
'\'CREATE DATABASE "dbname" '
|
||||
'WITH TABLESPACE = testspace OWNER = "otheruser"\'',
|
||||
host='testhost', password='foo', runas='foo', port='testport')
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None,
|
||||
'stdout': test_list_db_csv}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None,
|
||||
'stdout': test_list_db_csv}))
|
||||
def test_db_exists(self):
|
||||
ret = postgres.db_exists(
|
||||
'test_db',
|
||||
@ -91,8 +101,9 @@ class PostgresTestCase(TestCase):
|
||||
)
|
||||
self.assertTrue(ret)
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None,
|
||||
'stdout': test_list_db_csv}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None,
|
||||
'stdout': test_list_db_csv}))
|
||||
def test_db_list(self):
|
||||
ret = postgres.db_list(
|
||||
user='testuser',
|
||||
@ -103,16 +114,27 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
self.assertDictEqual(ret, {
|
||||
'test_db': {'Encoding': 'LATIN1', 'Ctype': 'en_US', 'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'test_db': {'Encoding': 'LATIN1', 'Ctype': 'en_US',
|
||||
'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'Owner': 'postgres', 'Access privileges': ''},
|
||||
'template1': {'Encoding': 'LATIN1', 'Ctype': 'en_US', 'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'Owner': 'postgres', 'Access privileges': '{=c/postgres,postgres=CTc/postgres}'},
|
||||
'template0': {'Encoding': 'LATIN1', 'Ctype': 'en_US', 'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'Owner': 'postgres', 'Access privileges': '{=c/postgres,postgres=CTc/postgres}'},
|
||||
'postgres': {'Encoding': 'LATIN1', 'Ctype': 'en_US', 'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'template1': {'Encoding': 'LATIN1', 'Ctype': 'en_US',
|
||||
'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'Owner': 'postgres',
|
||||
'Access privileges': (
|
||||
'{=c/postgres,postgres=CTc/postgres}'
|
||||
)},
|
||||
'template0': {'Encoding': 'LATIN1', 'Ctype': 'en_US',
|
||||
'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'Owner': 'postgres',
|
||||
'Access privileges': (
|
||||
'{=c/postgres,postgres=CTc/postgres}'
|
||||
)},
|
||||
'postgres': {'Encoding': 'LATIN1', 'Ctype': 'en_US',
|
||||
'Tablespace': 'pg_default', 'Collate': 'en_US',
|
||||
'Owner': 'postgres', 'Access privileges': ''}})
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
def test_db_remove(self):
|
||||
postgres.db_remove(
|
||||
'test_db',
|
||||
@ -124,10 +146,13 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
postgres._run_psql.assert_called_once_with(
|
||||
"/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_db -c 'DROP DATABASE test_db'",
|
||||
host='testhost', password='foo', runas='foo')
|
||||
"/usr/bin/pgsql --no-align --no-readline --username testuser "
|
||||
"--host testhost --port testport --dbname maint_db "
|
||||
"-c 'DROP DATABASE test_db'",
|
||||
host='testhost', password='foo', runas='foo', port='testport')
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=False))
|
||||
def test_group_create(self):
|
||||
postgres.group_create(
|
||||
@ -147,10 +172,13 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
self.assertTrue(re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_db -c (\'|\")CREATE ROLE',
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser '
|
||||
'--host testhost --port testport '
|
||||
'--dbname maint_db -c (\'|\")CREATE ROLE',
|
||||
postgres._run_psql.call_args[0][0]))
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=True))
|
||||
def test_group_remove(self):
|
||||
postgres.group_remove(
|
||||
@ -163,10 +191,13 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
postgres._run_psql.assert_called_once_with(
|
||||
"/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_db -c 'DROP ROLE testgroup'",
|
||||
host='testhost', password='foo', runas='foo', run_cmd='cmd.run')
|
||||
"/usr/bin/pgsql --no-align --no-readline --username testuser "
|
||||
"--host testhost --port testport "
|
||||
"--dbname maint_db -c 'DROP ROLE testgroup'",
|
||||
host='testhost', password='foo', runas='foo', port='testport')
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=True))
|
||||
def test_group_update(self):
|
||||
postgres.group_update(
|
||||
@ -185,11 +216,14 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
self.assertTrue(re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_db -c (\'|\")ALTER ROLE testgroup WITH UNENCRYPTED PASSWORD',
|
||||
'.*'
|
||||
'(\'|\")ALTER.* testgroup .* UNENCRYPTED PASSWORD',
|
||||
postgres._run_psql.call_args[0][0]))
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=False))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists',
|
||||
Mock(return_value=False))
|
||||
def test_user_create(self):
|
||||
postgres.user_create(
|
||||
'testuser',
|
||||
@ -198,7 +232,9 @@ class PostgresTestCase(TestCase):
|
||||
port='testport',
|
||||
maintenance_db='maint_test',
|
||||
password='test_pass',
|
||||
login=True,
|
||||
createdb=False,
|
||||
createroles=False,
|
||||
createuser=False,
|
||||
encrypted=False,
|
||||
superuser=False,
|
||||
@ -207,26 +243,38 @@ class PostgresTestCase(TestCase):
|
||||
groups='test_groups',
|
||||
runas='foo'
|
||||
)
|
||||
call = postgres._run_psql.call_args[0][0]
|
||||
self.assertTrue(re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser --host testhost --port testport --dbname maint_test -c (\'|\")CREATE USER',
|
||||
postgres._run_psql.call_args[0][0]))
|
||||
'/usr/bin/pgsql --no-align --no-readline --username testuser'
|
||||
' --host testhost --port testport'
|
||||
' --dbname maint_test -c (\'|\")CREATE ROLE',
|
||||
call))
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.version', Mock(return_value='9.1'))
|
||||
@patch('salt.modules.postgres.psql_query', Mock(return_value=[
|
||||
{
|
||||
'name': 'test_user',
|
||||
'superuser': 't',
|
||||
'inherits privileges': 't',
|
||||
'can create roles': 't',
|
||||
'can create databases': 't',
|
||||
'can update system catalogs': 't',
|
||||
'can login': 't',
|
||||
'replication': None,
|
||||
'password': 'test_password',
|
||||
'connections': '-1',
|
||||
'defaults variables': None
|
||||
}]))
|
||||
for i in (
|
||||
'INHERIT NOCREATEDB NOCREATEROLE '
|
||||
'NOSUPERUSER NOREPLICATION LOGIN PASSWORD'
|
||||
).split():
|
||||
self.assertTrue(i in call, '{0} not in {1}'.format(i, call))
|
||||
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.version',
|
||||
Mock(return_value='9.1'))
|
||||
@patch('salt.modules.postgres.psql_query',
|
||||
Mock(return_value=[
|
||||
{
|
||||
'name': 'test_user',
|
||||
'superuser': 't',
|
||||
'inherits privileges': 't',
|
||||
'can create roles': 't',
|
||||
'can create databases': 't',
|
||||
'can update system catalogs': 't',
|
||||
'can login': 't',
|
||||
'replication': None,
|
||||
'password': 'test_password',
|
||||
'connections': '-1',
|
||||
'defaults variables': None
|
||||
}]))
|
||||
def test_user_exists(self):
|
||||
ret = postgres.user_exists(
|
||||
'test_user',
|
||||
@ -239,21 +287,24 @@ class PostgresTestCase(TestCase):
|
||||
)
|
||||
self.assertTrue(ret)
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.version', Mock(return_value='9.1'))
|
||||
@patch('salt.modules.postgres.psql_query', Mock(return_value=[
|
||||
{
|
||||
'name': 'test_user',
|
||||
'superuser': 't',
|
||||
'inherits privileges': 't',
|
||||
'can create roles': 't',
|
||||
'can create databases': 't',
|
||||
'can update system catalogs': 't',
|
||||
'can login': 't',
|
||||
'replication': None,
|
||||
'connections': '-1',
|
||||
'defaults variables': None
|
||||
}]))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.version',
|
||||
Mock(return_value='9.1'))
|
||||
@patch('salt.modules.postgres.psql_query',
|
||||
Mock(return_value=[
|
||||
{
|
||||
'name': 'test_user',
|
||||
'superuser': 't',
|
||||
'inherits privileges': 't',
|
||||
'can create roles': 't',
|
||||
'can create databases': 't',
|
||||
'can update system catalogs': 't',
|
||||
'can login': 't',
|
||||
'replication': None,
|
||||
'connections': '-1',
|
||||
'defaults variables': None
|
||||
}]))
|
||||
def test_user_list(self):
|
||||
ret = postgres.user_list(
|
||||
'test_user',
|
||||
@ -265,11 +316,19 @@ class PostgresTestCase(TestCase):
|
||||
)
|
||||
|
||||
self.assertDictEqual(ret, {
|
||||
'test_user': {'superuser': True, 'defaults variables': None, 'can create databases': True,
|
||||
'can create roles': True, 'connections': None, 'replication': None, 'expiry time': None,
|
||||
'can login': True, 'can update system catalogs': True, 'inherits privileges': True}})
|
||||
'test_user': {'superuser': True,
|
||||
'defaults variables': None,
|
||||
'can create databases': True,
|
||||
'can create roles': True,
|
||||
'connections': None,
|
||||
'replication': None,
|
||||
'expiry time': None,
|
||||
'can login': True,
|
||||
'can update system catalogs': True,
|
||||
'inherits privileges': True}})
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.version', Mock(return_value='9.1'))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=True))
|
||||
def test_user_remove(self):
|
||||
@ -283,10 +342,14 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
postgres._run_psql.assert_called_once_with(
|
||||
"/usr/bin/pgsql --no-align --no-readline --username test_user --host test_host --port test_port --dbname maint_db -c 'DROP ROLE test_user'",
|
||||
host='test_host', password='test_password', runas='foo', run_cmd='cmd.run')
|
||||
"/usr/bin/pgsql --no-align --no-readline --username test_user "
|
||||
"--host test_host --port test_port "
|
||||
"--dbname maint_db -c 'DROP ROLE test_user'",
|
||||
host='test_host', port='test_port',
|
||||
password='test_password', runas='foo')
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=True))
|
||||
def test_user_update(self):
|
||||
postgres.user_update(
|
||||
@ -297,18 +360,92 @@ class PostgresTestCase(TestCase):
|
||||
maintenance_db='test_maint',
|
||||
password='test_pass',
|
||||
createdb=False,
|
||||
createroles=False,
|
||||
createuser=False,
|
||||
encrypted=False,
|
||||
inherit=True,
|
||||
login=True,
|
||||
replication=False,
|
||||
rolepassword='test_role_pass',
|
||||
groups='test_groups',
|
||||
runas='foo'
|
||||
)
|
||||
self.assertTrue(re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username test_user --host test_host --port test_port --dbname test_maint -c (\'|\")ALTER ROLE test_username',
|
||||
postgres._run_psql.call_args[0][0]))
|
||||
self.assertTrue(
|
||||
re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username test_user '
|
||||
'--host test_host --port test_port --dbname test_maint '
|
||||
'-c \'ALTER ROLE test_username WITH INHERIT NOCREATEDB '
|
||||
'NOCREATEROLE NOSUPERUSER NOREPLICATION LOGIN '
|
||||
'UNENCRYPTED PASSWORD \'"\'"\'test_role_pass\'"\'"\';'
|
||||
' GRANT test_groups TO test_username\'',
|
||||
postgres._run_psql.call_args[0][0])
|
||||
)
|
||||
|
||||
@patch('salt.modules.postgres._run_psql', Mock(return_value={'retcode': None, 'stdout': '9.1.9'}))
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=True))
|
||||
def test_user_update2(self):
|
||||
postgres.user_update(
|
||||
'test_username',
|
||||
user='test_user',
|
||||
host='test_host',
|
||||
port='test_port',
|
||||
maintenance_db='test_maint',
|
||||
password='test_pass',
|
||||
createdb=False,
|
||||
createroles=True,
|
||||
createuser=False,
|
||||
encrypted=False,
|
||||
inherit=True,
|
||||
login=True,
|
||||
replication=False,
|
||||
groups='test_groups',
|
||||
runas='foo'
|
||||
)
|
||||
self.assertTrue(
|
||||
re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username test_user '
|
||||
'--host test_host --port test_port --dbname test_maint '
|
||||
'-c \'ALTER ROLE test_username WITH INHERIT NOCREATEDB '
|
||||
'CREATEROLE NOSUPERUSER NOREPLICATION LOGIN;'
|
||||
' GRANT test_groups TO test_username\'',
|
||||
postgres._run_psql.call_args[0][0])
|
||||
)
|
||||
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None}))
|
||||
@patch('salt.modules.postgres.user_exists', Mock(return_value=True))
|
||||
def test_user_update3(self):
|
||||
postgres.user_update(
|
||||
'test_username',
|
||||
user='test_user',
|
||||
host='test_host',
|
||||
port='test_port',
|
||||
maintenance_db='test_maint',
|
||||
password='test_pass',
|
||||
createdb=False,
|
||||
createroles=True,
|
||||
createuser=False,
|
||||
encrypted=False,
|
||||
inherit=True,
|
||||
login=True,
|
||||
rolepassword=False,
|
||||
replication=False,
|
||||
groups='test_groups',
|
||||
runas='foo'
|
||||
)
|
||||
self.assertTrue(
|
||||
re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username test_user '
|
||||
'--host test_host --port test_port --dbname test_maint '
|
||||
'-c \'ALTER ROLE test_username WITH INHERIT NOCREATEDB '
|
||||
'CREATEROLE NOSUPERUSER NOREPLICATION LOGIN NOPASSWORD;'
|
||||
' GRANT test_groups TO test_username\'',
|
||||
postgres._run_psql.call_args[0][0])
|
||||
)
|
||||
|
||||
@patch('salt.modules.postgres._run_psql',
|
||||
Mock(return_value={'retcode': None, 'stdout': '9.1.9'}))
|
||||
def test_version(self):
|
||||
postgres.version(
|
||||
user='test_user',
|
||||
@ -319,11 +456,161 @@ class PostgresTestCase(TestCase):
|
||||
runas='foo'
|
||||
)
|
||||
self.assertTrue(re.match(
|
||||
'/usr/bin/pgsql --no-align --no-readline --username test_user --host test_host --port test_port --dbname test_maint -c (\'|\")SELECT setting FROM pg_catalog.pg_settings',
|
||||
'/usr/bin/pgsql --no-align --no-readline --username test_user '
|
||||
'--host test_host --port test_port '
|
||||
'--dbname test_maint '
|
||||
'-c (\'|\")SELECT setting FROM pg_catalog.pg_settings',
|
||||
postgres._run_psql.call_args[0][0]))
|
||||
|
||||
@patch('salt.modules.postgres.psql_query',
|
||||
Mock(return_value=[{'extname': "foo", 'extversion': "1"}]))
|
||||
def test_installed_extensions(self):
|
||||
exts = postgres.installed_extensions()
|
||||
self.assertEqual(
|
||||
exts,
|
||||
{'foo': {'extversion': '1', 'extname': 'foo'}}
|
||||
)
|
||||
|
||||
@patch('salt.modules.postgres.psql_query',
|
||||
Mock(return_value=[{'name': "foo", 'default_version': "1"}]))
|
||||
def test_available_extensions(self):
|
||||
exts = postgres.available_extensions()
|
||||
self.assertEqual(
|
||||
exts,
|
||||
{'foo': {'default_version': '1', 'name': 'foo'}}
|
||||
)
|
||||
|
||||
@patch('salt.modules.postgres.installed_extensions',
|
||||
Mock(side_effect=[{}, {}]))
|
||||
@patch('salt.modules.postgres._psql_prepare_and_run',
|
||||
Mock(return_value=None))
|
||||
@patch('salt.modules.postgres.available_extensions',
|
||||
Mock(return_value={'foo': {'default_version': '1', 'name': 'foo'}}))
|
||||
def test_drop_extension2(self):
|
||||
self.assertEqual(postgres.drop_extension('foo'), True)
|
||||
|
||||
@patch('salt.modules.postgres.installed_extensions',
|
||||
Mock(side_effect=[{'foo': {'extversion': '1', 'extname': 'foo'}},
|
||||
{}]))
|
||||
@patch('salt.modules.postgres._psql_prepare_and_run',
|
||||
Mock(return_value=None))
|
||||
@patch('salt.modules.postgres.available_extensions',
|
||||
Mock(return_value={'foo': {'default_version': '1', 'name': 'foo'}}))
|
||||
def test_drop_extension3(self):
|
||||
self.assertEqual(postgres.drop_extension('foo'), True)
|
||||
|
||||
@patch('salt.modules.postgres.installed_extensions',
|
||||
Mock(side_effect=[{'foo': {'extversion': '1', 'extname': 'foo'}},
|
||||
{'foo': {'extversion': '1', 'extname': 'foo'}}]))
|
||||
@patch('salt.modules.postgres._psql_prepare_and_run',
|
||||
Mock(return_value=None))
|
||||
@patch('salt.modules.postgres.available_extensions',
|
||||
Mock(return_value={'foo': {'default_version': '1', 'name': 'foo'}}))
|
||||
def test_drop_extension1(self):
|
||||
self.assertEqual(postgres.drop_extension('foo'), False)
|
||||
|
||||
@patch('salt.modules.postgres.installed_extensions',
|
||||
Mock(return_value=
|
||||
{'foo': {'extversion': '0.8',
|
||||
'extrelocatable': 't',
|
||||
'schema_name': 'foo',
|
||||
'extname': 'foo'}},
|
||||
))
|
||||
@patch('salt.modules.postgres.available_extensions',
|
||||
Mock(return_value={'foo': {'default_version': '1.4',
|
||||
'name': 'foo'}}))
|
||||
def test_create_mtdata(self):
|
||||
ret = postgres.create_metadata('foo', schema='bar', ext_version='1.4')
|
||||
self.assertTrue(postgres._EXTENSION_INSTALLED in ret)
|
||||
self.assertTrue(postgres._EXTENSION_TO_UPGRADE in ret)
|
||||
self.assertTrue(postgres._EXTENSION_TO_MOVE in ret)
|
||||
ret = postgres.create_metadata('foo', schema='foo', ext_version='0.4')
|
||||
self.assertTrue(postgres._EXTENSION_INSTALLED in ret)
|
||||
self.assertFalse(postgres._EXTENSION_TO_UPGRADE in ret)
|
||||
self.assertFalse(postgres._EXTENSION_TO_MOVE in ret)
|
||||
ret = postgres.create_metadata('foo')
|
||||
self.assertTrue(postgres._EXTENSION_INSTALLED in ret)
|
||||
self.assertFalse(postgres._EXTENSION_TO_UPGRADE in ret)
|
||||
self.assertFalse(postgres._EXTENSION_TO_MOVE in ret)
|
||||
ret = postgres.create_metadata('foobar')
|
||||
self.assertTrue(postgres._EXTENSION_NOT_INSTALLED in ret)
|
||||
self.assertFalse(postgres._EXTENSION_INSTALLED in ret)
|
||||
self.assertFalse(postgres._EXTENSION_TO_UPGRADE in ret)
|
||||
self.assertFalse(postgres._EXTENSION_TO_MOVE in ret)
|
||||
|
||||
@patch('salt.modules.postgres.create_metadata',
|
||||
Mock(side_effect=[
|
||||
# create suceeded
|
||||
[postgres._EXTENSION_NOT_INSTALLED],
|
||||
[postgres._EXTENSION_INSTALLED],
|
||||
[postgres._EXTENSION_NOT_INSTALLED],
|
||||
[postgres._EXTENSION_INSTALLED],
|
||||
# create failed
|
||||
[postgres._EXTENSION_NOT_INSTALLED],
|
||||
[postgres._EXTENSION_NOT_INSTALLED],
|
||||
# move+upgrade succeeded
|
||||
[postgres._EXTENSION_TO_MOVE,
|
||||
postgres._EXTENSION_TO_UPGRADE,
|
||||
postgres._EXTENSION_INSTALLED],
|
||||
[postgres._EXTENSION_INSTALLED],
|
||||
# move suceeded
|
||||
[postgres._EXTENSION_TO_MOVE,
|
||||
postgres._EXTENSION_INSTALLED],
|
||||
[postgres._EXTENSION_INSTALLED],
|
||||
# upgrade suceeded
|
||||
[postgres._EXTENSION_TO_UPGRADE,
|
||||
postgres._EXTENSION_INSTALLED],
|
||||
[postgres._EXTENSION_INSTALLED],
|
||||
# upgrade failed
|
||||
[postgres._EXTENSION_TO_UPGRADE, postgres._EXTENSION_INSTALLED],
|
||||
[postgres._EXTENSION_TO_UPGRADE, postgres._EXTENSION_INSTALLED],
|
||||
# move failed
|
||||
[postgres._EXTENSION_TO_MOVE, postgres._EXTENSION_INSTALLED],
|
||||
[postgres._EXTENSION_TO_MOVE, postgres._EXTENSION_INSTALLED],
|
||||
]))
|
||||
@patch('salt.modules.postgres._psql_prepare_and_run',
|
||||
Mock(return_value=None))
|
||||
@patch('salt.modules.postgres.available_extensions',
|
||||
Mock(return_value={'foo': {'default_version': '1.4',
|
||||
'name': 'foo'}}))
|
||||
def test_create_extension_newerthan(self):
|
||||
'''
|
||||
scenario of creating upgrading extensions with possible schema and
|
||||
version specifications
|
||||
'''
|
||||
self.assertTrue(postgres.create_extension('foo'))
|
||||
self.assertTrue(re.match(
|
||||
'CREATE EXTENSION IF NOT EXISTS foo ;',
|
||||
postgres._psql_prepare_and_run.call_args[0][0][1]))
|
||||
self.assertTrue(postgres.create_extension(
|
||||
'foo', schema='a', ext_version='b', from_version='c'))
|
||||
self.assertTrue(re.match(
|
||||
'CREATE EXTENSION IF NOT EXISTS foo '
|
||||
'WITH SCHEMA a VERSION b FROM c ;',
|
||||
postgres._psql_prepare_and_run.call_args[0][0][1]))
|
||||
self.assertFalse(postgres.create_extension('foo'))
|
||||
ret = postgres.create_extension('foo', ext_version='a', schema='b')
|
||||
self.assertTrue(ret)
|
||||
self.assertTrue(re.match(
|
||||
'ALTER EXTENSION foo SET SCHEMA b;'
|
||||
' ALTER EXTENSION foo UPDATE TO a;',
|
||||
postgres._psql_prepare_and_run.call_args[0][0][1]))
|
||||
ret = postgres.create_extension('foo', ext_version='a', schema='b')
|
||||
self.assertTrue(ret)
|
||||
self.assertTrue(re.match(
|
||||
'ALTER EXTENSION foo SET SCHEMA b;',
|
||||
postgres._psql_prepare_and_run.call_args[0][0][1]))
|
||||
ret = postgres.create_extension('foo', ext_version='a', schema='b')
|
||||
self.assertTrue(ret)
|
||||
self.assertTrue(re.match(
|
||||
'ALTER EXTENSION foo UPDATE TO a;',
|
||||
postgres._psql_prepare_and_run.call_args[0][0][1]))
|
||||
self.assertFalse(postgres.create_extension(
|
||||
'foo', ext_version='a', schema='b'))
|
||||
self.assertFalse(postgres.create_extension(
|
||||
'foo', ext_version='a', schema='b'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
|
||||
run_tests(PostgresTestCase, needs_daemon=False)
|
||||
|
225
tests/unit/states/postgres_test.py
Normal file
225
tests/unit/states/postgres_test.py
Normal file
@ -0,0 +1,225 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Salt Testing libs
|
||||
from salttesting import skipIf, TestCase
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
from salttesting.mock import NO_MOCK, NO_MOCK_REASON, Mock, patch
|
||||
import re
|
||||
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
# Import salt libs
|
||||
|
||||
from salt.modules import postgres as postgresmod
|
||||
|
||||
from salt.states import (
|
||||
postgres_database,
|
||||
postgres_user,
|
||||
postgres_group,
|
||||
postgres_extension,
|
||||
)
|
||||
MODS = (
|
||||
postgres_database,
|
||||
postgres_user,
|
||||
postgres_group,
|
||||
postgres_extension,
|
||||
)
|
||||
|
||||
|
||||
OPTS = {'test': False}
|
||||
|
||||
for postgres in MODS:
|
||||
postgres.__grains__ = None # in order to stub it w/patch below
|
||||
postgres.__salt__ = None # in order to stub it w/patch below
|
||||
postgres.__opts__ = OPTS # in order to stub it w/patch below
|
||||
|
||||
if NO_MOCK is False:
|
||||
SALT_STUB = {
|
||||
'config.option': Mock(),
|
||||
'cmd.run_all': Mock(),
|
||||
'file.chown': Mock(),
|
||||
'file.remove': Mock(),
|
||||
}
|
||||
else:
|
||||
SALT_STUB = {}
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
@patch.multiple(postgres,
|
||||
__grains__={'os_family': 'Linux'},
|
||||
__salt__=SALT_STUB)
|
||||
@patch('salt.utils.which', Mock(return_value='/usr/bin/pgsql'))
|
||||
class PostgresTestCase(TestCase):
|
||||
|
||||
@patch.dict(SALT_STUB, {
|
||||
'postgres.create_metadata': Mock(side_effect=[
|
||||
[postgresmod._EXTENSION_NOT_INSTALLED],
|
||||
[postgresmod._EXTENSION_TO_MOVE, postgresmod._EXTENSION_INSTALLED],
|
||||
|
||||
]),
|
||||
'postgres.create_extension': Mock(side_effect=[
|
||||
False, False,
|
||||
]),
|
||||
})
|
||||
def test_present_failed(self):
|
||||
'''
|
||||
scenario of creating upgrading extensions with possible schema and
|
||||
version specifications
|
||||
'''
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Failed to install extension foo',
|
||||
'changes': {}, 'name': 'foo', 'result': False},
|
||||
)
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Failed to upgrade extension foo',
|
||||
'changes': {}, 'name': 'foo', 'result': False}
|
||||
)
|
||||
|
||||
@patch.dict(SALT_STUB, {
|
||||
'postgres.create_metadata': Mock(side_effect=[
|
||||
[postgresmod._EXTENSION_NOT_INSTALLED],
|
||||
[postgresmod._EXTENSION_INSTALLED],
|
||||
[postgresmod._EXTENSION_TO_MOVE, postgresmod._EXTENSION_INSTALLED],
|
||||
|
||||
]),
|
||||
'postgres.create_extension': Mock(side_effect=[
|
||||
True, True, True,
|
||||
]),
|
||||
})
|
||||
def test_present(self):
|
||||
'''
|
||||
scenario of creating upgrading extensions with possible schema and
|
||||
version specifications
|
||||
'''
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'The extension foo has been installed',
|
||||
'changes': {}, 'name': 'foo', 'result': True}
|
||||
)
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Extention foo is already present',
|
||||
'changes': {}, 'name': 'foo', 'result': True}
|
||||
)
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'The extension foo has been upgradeed',
|
||||
'changes': {}, 'name': 'foo', 'result': True}
|
||||
)
|
||||
|
||||
@patch.dict(OPTS, {'test': True})
|
||||
@patch.dict(SALT_STUB, {
|
||||
'postgres.create_metadata': Mock(side_effect=[
|
||||
[postgresmod._EXTENSION_NOT_INSTALLED],
|
||||
[postgresmod._EXTENSION_INSTALLED],
|
||||
[postgresmod._EXTENSION_TO_MOVE, postgresmod._EXTENSION_INSTALLED],
|
||||
|
||||
]),
|
||||
'postgres.create_extension': Mock(side_effect=[
|
||||
True, True, True,
|
||||
]),
|
||||
})
|
||||
def test_presenttest(self):
|
||||
'''
|
||||
scenario of creating upgrading extensions with possible schema and
|
||||
version specifications
|
||||
'''
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Extension foo is set to be installed',
|
||||
'changes': {}, 'name': 'foo', 'result': None}
|
||||
|
||||
)
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': "Extension foo is set to be created",
|
||||
'changes': {}, 'name': 'foo', 'result': None}
|
||||
|
||||
)
|
||||
ret = postgres_extension.present('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': "Extension foo is set to be upgraded",
|
||||
'changes': {}, 'name': 'foo', 'result': None}
|
||||
)
|
||||
|
||||
@patch.dict(SALT_STUB, {
|
||||
'postgres.is_installed_extension': Mock(side_effect=[
|
||||
True, False,
|
||||
]),
|
||||
'postgres.drop_extension': Mock(side_effect=[
|
||||
True, True,
|
||||
]),
|
||||
})
|
||||
def test_absent(self):
|
||||
'''
|
||||
scenario of creating upgrading extensions with possible schema and
|
||||
version specifications
|
||||
'''
|
||||
ret = postgres_extension.absent('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Extension foo has been removed',
|
||||
'changes': {'foo': 'Absent'}, 'name': 'foo', 'result': True}
|
||||
)
|
||||
ret = postgres_extension.absent('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': (
|
||||
'Extension foo is not present, '
|
||||
'so it cannot be removed'),
|
||||
'changes': {}, 'name': 'foo', 'result': True}
|
||||
|
||||
)
|
||||
|
||||
@patch.dict(OPTS, {'test': False})
|
||||
@patch.dict(SALT_STUB, {
|
||||
'postgres.is_installed_extension': Mock(side_effect=[
|
||||
True, True,
|
||||
]),
|
||||
'postgres.drop_extension': Mock(side_effect=[
|
||||
False, False,
|
||||
]),
|
||||
})
|
||||
def test_absent_failed(self):
|
||||
'''
|
||||
scenario of creating upgrading extensions with possible schema and
|
||||
version specifications
|
||||
'''
|
||||
ret = postgres_extension.absent('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Extension foo failed to be removed',
|
||||
'changes': {}, 'name': 'foo', 'result': False}
|
||||
)
|
||||
|
||||
@patch.dict(OPTS, {'test': True})
|
||||
@patch.dict(SALT_STUB, {
|
||||
'postgres.is_installed_extension': Mock(side_effect=[
|
||||
True, True,
|
||||
]),
|
||||
'postgres.drop_extension': Mock(side_effect=[
|
||||
False, False,
|
||||
]),
|
||||
})
|
||||
def test_absent_failedtest(self):
|
||||
ret = postgres_extension.absent('foo')
|
||||
self.assertEqual(
|
||||
ret,
|
||||
{'comment': 'Extension foo is set to be removed',
|
||||
'changes': {}, 'name': 'foo', 'result': None}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(PostgresTestCase, needs_daemon=False)
|
Loading…
Reference in New Issue
Block a user