File: //usr/lib/python2.7/site-packages/salt/modules/mongodb.py
# -*- coding: utf-8 -*-
'''
Module to provide MongoDB functionality to Salt
:configuration: This module uses PyMongo, and accepts configuration details as
parameters as well as configuration settings::
mongodb.host: 'localhost'
mongodb.port: 27017
mongodb.user: ''
mongodb.password: ''
This data can also be passed into pillar. Options passed into opts will
overwrite options passed into pillar.
'''
from __future__ import absolute_import, print_function, unicode_literals
# Import python libs
import logging
import re
import sys
# Import salt libs
import salt.utils.json
from salt.utils.versions import LooseVersion as _LooseVersion
from salt.exceptions import get_error_message as _get_error_message
# Import third party libs
from salt.ext import six
try:
import pymongo
HAS_MONGODB = True
except ImportError:
HAS_MONGODB = False
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load this module if pymongo is installed
'''
if HAS_MONGODB:
return 'mongodb'
else:
return (False, 'The mongodb execution module cannot be loaded: the pymongo library is not available.')
def _connect(user=None, password=None, host=None, port=None, database='admin', authdb=None):
'''
Returns a tuple of (user, host, port) with config, pillar, or default
values assigned to missing values.
'''
if not user:
user = __salt__['config.option']('mongodb.user')
if not password:
password = __salt__['config.option']('mongodb.password')
if not host:
host = __salt__['config.option']('mongodb.host')
if not port:
port = __salt__['config.option']('mongodb.port')
if not authdb:
authdb = database
try:
conn = pymongo.MongoClient(host=host, port=port)
mdb = pymongo.database.Database(conn, database)
if user and password:
mdb.authenticate(user, password, source=authdb)
except pymongo.errors.PyMongoError:
log.error('Error connecting to database %s', database)
return False
return conn
def _to_dict(objects):
'''
Potentially interprets a string as JSON for usage with mongo
'''
try:
if isinstance(objects, six.string_types):
objects = salt.utils.json.loads(objects)
except ValueError as err:
log.error("Could not parse objects: %s", err)
six.reraise(*sys.exc_info())
return objects
def db_list(user=None, password=None, host=None, port=None, authdb=None):
'''
List all MongoDB databases
CLI Example:
.. code-block:: bash
salt '*' mongodb.db_list <user> <password> <host> <port>
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
log.info('Listing databases')
return conn.database_names()
except pymongo.errors.PyMongoError as err:
log.error(err)
return six.text_type(err)
def db_exists(name, user=None, password=None, host=None, port=None, authdb=None):
'''
Checks if a database exists in MongoDB
CLI Example:
.. code-block:: bash
salt '*' mongodb.db_exists <name> <user> <password> <host> <port>
'''
dbs = db_list(user, password, host, port, authdb=authdb)
if isinstance(dbs, six.string_types):
return False
return name in dbs
def db_remove(name, user=None, password=None, host=None, port=None, authdb=None):
'''
Remove a MongoDB database
CLI Example:
.. code-block:: bash
salt '*' mongodb.db_remove <name> <user> <password> <host> <port>
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
log.info('Removing database %s', name)
conn.drop_database(name)
except pymongo.errors.PyMongoError as err:
log.error('Removing database %s failed with error: %s', name, err)
return six.text_type(err)
return True
def _version(mdb):
return mdb.command('buildInfo')['version']
def version(user=None, password=None, host=None, port=None, database='admin', authdb=None):
'''
Get MongoDB instance version
CLI Example:
.. code-block:: bash
salt '*' mongodb.version <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
err_msg = "Failed to connect to MongoDB database {0}:{1}".format(host, port)
log.error(err_msg)
return (False, err_msg)
try:
mdb = pymongo.database.Database(conn, database)
return _version(mdb)
except pymongo.errors.PyMongoError as err:
log.error('Listing users failed with error: %s', err)
return six.text_type(err)
def user_find(name, user=None, password=None, host=None, port=None,
database='admin', authdb=None):
'''
Get single user from MongoDB
CLI Example:
.. code-block:: bash
salt '*' mongodb.user_find <name> <user> <password> <host> <port> <database> <authdb>
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
err_msg = "Failed to connect to MongoDB database {0}:{1}".format(host, port)
log.error(err_msg)
return (False, err_msg)
mdb = pymongo.database.Database(conn, database)
try:
return mdb.command("usersInfo", name)["users"]
except pymongo.errors.PyMongoError as err:
log.error('Listing users failed with error: %s', err)
return (False, six.text_type(err))
def user_list(user=None, password=None, host=None, port=None, database='admin', authdb=None):
'''
List users of a MongoDB database
CLI Example:
.. code-block:: bash
salt '*' mongodb.user_list <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
log.info('Listing users')
mdb = pymongo.database.Database(conn, database)
output = []
mongodb_version = _version(mdb)
if _LooseVersion(mongodb_version) >= _LooseVersion('2.6'):
for user in mdb.command('usersInfo')['users']:
output.append(
{'user': user['user'],
'roles': user['roles']}
)
else:
for user in mdb.system.users.find():
output.append(
{'user': user['user'],
'readOnly': user.get('readOnly', 'None')}
)
return output
except pymongo.errors.PyMongoError as err:
log.error('Listing users failed with error: %s', err)
return six.text_type(err)
def user_exists(name, user=None, password=None, host=None, port=None,
database='admin', authdb=None):
'''
Checks if a user exists in MongoDB
CLI Example:
.. code-block:: bash
salt '*' mongodb.user_exists <name> <user> <password> <host> <port> <database>
'''
users = user_list(user, password, host, port, database, authdb)
if isinstance(users, six.string_types):
return 'Failed to connect to mongo database'
for user in users:
if name == dict(user).get('user'):
return True
return False
def user_create(name, passwd, user=None, password=None, host=None, port=None,
database='admin', authdb=None, roles=None):
'''
Create a MongoDB user
CLI Example:
.. code-block:: bash
salt '*' mongodb.user_create <user_name> <user_password> <roles> <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
return 'Failed to connect to mongo database'
if not roles:
roles = []
try:
log.info('Creating user %s', name)
mdb = pymongo.database.Database(conn, database)
mdb.add_user(name, passwd, roles=roles)
except pymongo.errors.PyMongoError as err:
log.error('Creating database %s failed with error: %s', name, err)
return six.text_type(err)
return True
def user_remove(name, user=None, password=None, host=None, port=None,
database='admin', authdb=None):
'''
Remove a MongoDB user
CLI Example:
.. code-block:: bash
salt '*' mongodb.user_remove <name> <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port)
if not conn:
return 'Failed to connect to mongo database'
try:
log.info('Removing user %s', name)
mdb = pymongo.database.Database(conn, database)
mdb.remove_user(name)
except pymongo.errors.PyMongoError as err:
log.error('Creating database %s failed with error: %s', name, err)
return six.text_type(err)
return True
def user_roles_exists(name, roles, database, user=None, password=None, host=None,
port=None, authdb=None):
'''
Checks if a user of a MongoDB database has specified roles
CLI Examples:
.. code-block:: bash
salt '*' mongodb.user_roles_exists johndoe '["readWrite"]' dbname admin adminpwd localhost 27017
.. code-block:: bash
salt '*' mongodb.user_roles_exists johndoe '[{"role": "readWrite", "db": "dbname" }, {"role": "read", "db": "otherdb"}]' dbname admin adminpwd localhost 27017
'''
try:
roles = _to_dict(roles)
except Exception: # pylint: disable=broad-except
return 'Roles provided in wrong format'
users = user_list(user, password, host, port, database, authdb)
if isinstance(users, six.string_types):
return 'Failed to connect to mongo database'
for user in users:
if name == dict(user).get('user'):
for role in roles:
# if the role was provided in the shortened form, we convert it to a long form
if not isinstance(role, dict):
role = {'role': role, 'db': database}
if role not in dict(user).get('roles', []):
return False
return True
return False
def user_grant_roles(name, roles, database, user=None, password=None, host=None,
port=None, authdb=None):
'''
Grant one or many roles to a MongoDB user
CLI Examples:
.. code-block:: bash
salt '*' mongodb.user_grant_roles johndoe '["readWrite"]' dbname admin adminpwd localhost 27017
.. code-block:: bash
salt '*' mongodb.user_grant_roles janedoe '[{"role": "readWrite", "db": "dbname" }, {"role": "read", "db": "otherdb"}]' dbname admin adminpwd localhost 27017
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
roles = _to_dict(roles)
except Exception: # pylint: disable=broad-except
return 'Roles provided in wrong format'
try:
log.info('Granting roles %s to user %s', roles, name)
mdb = pymongo.database.Database(conn, database)
mdb.command("grantRolesToUser", name, roles=roles)
except pymongo.errors.PyMongoError as err:
log.error('Granting roles %s to user %s failed with error: %s', roles, name, err)
return six.text_type(err)
return True
def user_revoke_roles(name, roles, database, user=None, password=None, host=None,
port=None, authdb=None):
'''
Revoke one or many roles to a MongoDB user
CLI Examples:
.. code-block:: bash
salt '*' mongodb.user_revoke_roles johndoe '["readWrite"]' dbname admin adminpwd localhost 27017
.. code-block:: bash
salt '*' mongodb.user_revoke_roles janedoe '[{"role": "readWrite", "db": "dbname" }, {"role": "read", "db": "otherdb"}]' dbname admin adminpwd localhost 27017
'''
conn = _connect(user, password, host, port, authdb=authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
roles = _to_dict(roles)
except Exception: # pylint: disable=broad-except
return 'Roles provided in wrong format'
try:
log.info('Revoking roles %s from user %s', roles, name)
mdb = pymongo.database.Database(conn, database)
mdb.command("revokeRolesFromUser", name, roles=roles)
except pymongo.errors.PyMongoError as err:
log.error('Revoking roles %s from user %s failed with error: %s', roles, name, err)
return six.text_type(err)
return True
def insert(objects, collection, user=None, password=None,
host=None, port=None, database='admin', authdb=None):
'''
Insert an object or list of objects into a collection
CLI Example:
.. code-block:: bash
salt '*' mongodb.insert '[{"foo": "FOO", "bar": "BAR"}, {"foo": "BAZ", "bar": "BAM"}]' mycollection <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, database, authdb)
if not conn:
return "Failed to connect to mongo database"
try:
objects = _to_dict(objects)
except Exception as err: # pylint: disable=broad-except
return err
try:
log.info("Inserting %r into %s.%s", objects, database, collection)
mdb = pymongo.database.Database(conn, database)
col = getattr(mdb, collection)
ids = col.insert(objects)
return ids
except pymongo.errors.PyMongoError as err:
log.error("Inserting objects %r failed with error %s", objects, err)
return err
def update_one(objects, collection, user=None, password=None, host=None, port=None, database='admin', authdb=None):
'''
Update an object into a collection
http://api.mongodb.com/python/current/api/pymongo/collection.html#pymongo.collection.Collection.update_one
.. versionadded:: 2016.11.0
CLI Example:
.. code-block:: bash
salt '*' mongodb.update_one '{"_id": "my_minion"} {"bar": "BAR"}' mycollection <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, database, authdb)
if not conn:
return "Failed to connect to mongo database"
objects = six.text_type(objects)
objs = re.split(r'}\s+{', objects)
if len(objs) is not 2:
return "Your request does not contain a valid " + \
"'{_\"id\": \"my_id\"} {\"my_doc\": \"my_val\"}'"
objs[0] = objs[0] + '}'
objs[1] = '{' + objs[1]
document = []
for obj in objs:
try:
obj = _to_dict(obj)
document.append(obj)
except Exception as err: # pylint: disable=broad-except
return err
_id_field = document[0]
_update_doc = document[1]
# need a string to perform the test, so using objs[0]
test_f = find(collection,
objs[0],
user,
password,
host,
port,
database,
authdb)
if not isinstance(test_f, list):
return 'The find result is not well formatted. An error appears; cannot update.'
elif len(test_f) < 1:
return 'Did not find any result. You should try an insert before.'
elif len(test_f) > 1:
return 'Too many results. Please try to be more specific.'
else:
try:
log.info("Updating %r into %s.%s", _id_field, database, collection)
mdb = pymongo.database.Database(conn, database)
col = getattr(mdb, collection)
ids = col.update_one(_id_field, {'$set': _update_doc})
nb_mod = ids.modified_count
return "{0} objects updated".format(nb_mod)
except pymongo.errors.PyMongoError as err:
log.error('Updating object %s failed with error %s', objects, err)
return err
def find(collection, query=None, user=None, password=None,
host=None, port=None, database='admin', authdb=None):
'''
Find an object or list of objects in a collection
CLI Example:
.. code-block:: bash
salt '*' mongodb.find mycollection '[{"foo": "FOO", "bar": "BAR"}]' <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, database, authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
query = _to_dict(query)
except Exception as err: # pylint: disable=broad-except
return err
try:
log.info("Searching for %r in %s", query, collection)
mdb = pymongo.database.Database(conn, database)
col = getattr(mdb, collection)
ret = col.find(query)
return list(ret)
except pymongo.errors.PyMongoError as err:
log.error("Searching objects failed with error: %s", err)
return err
def remove(collection, query=None, user=None, password=None,
host=None, port=None, database='admin', w=1, authdb=None):
'''
Remove an object or list of objects into a collection
CLI Example:
.. code-block:: bash
salt '*' mongodb.remove mycollection '[{"foo": "FOO", "bar": "BAR"}, {"foo": "BAZ", "bar": "BAM"}]' <user> <password> <host> <port> <database>
'''
conn = _connect(user, password, host, port, database, authdb)
if not conn:
return 'Failed to connect to mongo database'
try:
query = _to_dict(query)
except Exception as err: # pylint: disable=broad-except
return _get_error_message(err)
try:
log.info("Removing %r from %s", query, collection)
mdb = pymongo.database.Database(conn, database)
col = getattr(mdb, collection)
ret = col.remove(query, w=w)
return "{0} objects removed".format(ret['n'])
except pymongo.errors.PyMongoError as err:
log.error("Removing objects failed with error: %s", _get_error_message(err))
return _get_error_message(err)