blob: fb610faec098bac2658b5272881fedde33cba564 [file] [log] [blame]
#!/usr/bin/python
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: mongodb
short_description: A module which support some simple operations on MongoDB.
description:
- Including add user/insert document/create indexes in MongoDB
options:
connect_string:
description:
- The uri of mongodb server
required: true
database:
description:
- The name of the database you want to manipulate
required: true
user:
description:
- The name of the user to add or remove, required when use 'user' mode
required: false
default: null
password:
description:
- The password to use for the user, required when use 'user' mode
required: false
default: null
roles:
description:
- The roles of the user, it's a list of dict, each dict requires two fields: 'db' and 'role', required when use 'user' mode
required: false
default: null
collection:
required: false
description:
- The name of the collection you want to manipulate, required when use 'doc' or 'indexes' mode
doc:
required: false
description:
- The document you want to insert into MongoDB, required when use 'doc' mode
indexes:
required: false
description:
- The indexes you want to create in MongoDB, it's a list of dict, you can see the example for the usage, required when use 'index' mode
force_update:
required: false
description:
- Whether replace/update existing user or doc or raise DuplicateKeyError, default is false
mode:
required: false
default: user
choices: ['user', 'doc', 'index']
description:
- use 'user' mode if you want to add user, 'doc' mode to insert document, 'index' mode to create indexes
requirements: [ "pymongo" ]
author:
- "Jinag PengCheng"
'''
EXAMPLES = '''
# add user
- mongodb:
connect_string: mongodb://localhost:27017
database: admin
user: test
password: 123456
roles:
- db: test_database
role: read
force_update: true
# add doc
- mongodb:
connect_string: mongodb://localhost:27017
mode: doc
database: admin
collection: main
doc:
id: "id/document"
title: "the name of document"
content: "which doesn't matter"
force_update: true
# add indexes
- mongodb:
connect_string: mongodb://localhost:27017
mode: index
database: admin
collection: main
indexes:
- index:
- field: updated_at
direction: 1
- field: name
direction: -1
name: test-index
unique: true
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
try:
from pymongo import ASCENDING, DESCENDING, GEO2D, GEOHAYSTACK, GEOSPHERE, HASHED, TEXT
from pymongo import IndexModel
from pymongo import MongoClient
from pymongo.errors import DuplicateKeyError
except ImportError:
pass
# =========================================
# MongoDB module specific support methods.
#
class UnknownIndexPlugin(Exception):
pass
def check_params(params, mode, module):
missed_params = []
for key in OPERATIONS[mode]['required']:
if params[key] is None:
missed_params.append(key)
if missed_params:
module.fail_json(msg="missing required arguments: %s" % (",".join(missed_params)))
def _recreate_user(module, db, user, password, roles):
try:
db.command("dropUser", user)
db.command("createUser", user, pwd=password, roles=roles)
except Exception as e:
module.fail_json(msg='Unable to create user: %s' % to_native(e), exception=traceback.format_exc())
def user(module, client, db_name, **kwargs):
roles = kwargs['roles']
if roles is None:
roles = []
db = client[db_name]
try:
db.command("createUser", kwargs['user'], pwd=kwargs['password'], roles=roles)
except DuplicateKeyError as e:
if kwargs['force_update']:
_recreate_user(module, db, kwargs['user'], kwargs['password'], roles)
else:
module.fail_json(msg='Unable to create user: %s' % to_native(e), exception=traceback.format_exc())
except Exception as e:
module.fail_json(msg='Unable to create user: %s' % to_native(e), exception=traceback.format_exc())
module.exit_json(changed=True, user=kwargs['user'])
def doc(module, client, db_name, **kwargs):
coll = client[db_name][kwargs['collection']]
try:
coll.insert_one(kwargs['doc'])
except DuplicateKeyError as e:
if kwargs['force_update']:
try:
coll.replace_one({'_id': kwargs['doc']['_id']}, kwargs['doc'])
except Exception as e:
module.fail_json(msg='Unable to insert doc: %s' % to_native(e), exception=traceback.format_exc())
else:
module.fail_json(msg='Unable to insert doc: %s' % to_native(e), exception=traceback.format_exc())
except Exception as e:
module.fail_json(msg='Unable to insert doc: %s' % to_native(e), exception=traceback.format_exc())
kwargs['doc']['_id'] = str(kwargs['doc']['_id'])
module.exit_json(changed=True, doc=kwargs['doc'])
def _clean_index_direction(direction):
if direction in ["1", "-1"]:
direction = int(direction)
if direction not in [ASCENDING, DESCENDING, GEO2D, GEOHAYSTACK, GEOSPHERE, HASHED, TEXT]:
raise UnknownIndexPlugin("Unable to create indexes: Unknown index plugin: %s" % direction)
return direction
def _clean_index_options(options):
res = {}
supported_options = set(['name', 'unique', 'background', 'sparse', 'bucketSize', 'min', 'max', 'expireAfterSeconds'])
for key in set(options.keys()).intersection(supported_options):
res[key] = options[key]
if key in ['min', 'max', 'bucketSize', 'expireAfterSeconds']:
res[key] = int(res[key])
return res
def parse_indexes(idx):
keys = [(k['field'], _clean_index_direction(k['direction'])) for k in idx.pop('index')]
options = _clean_index_options(idx)
return IndexModel(keys, **options)
def index(module, client, db_name, **kwargs):
parsed_indexes = map(parse_indexes, kwargs['indexes'])
try:
coll = client[db_name][kwargs['collection']]
coll.create_indexes(parsed_indexes)
except Exception as e:
module.fail_json(msg='Unable to create indexes: %s' % to_native(e), exception=traceback.format_exc())
module.exit_json(changed=True, indexes=kwargs['indexes'])
OPERATIONS = {
'user': { 'function': user, 'params': ['user', 'password', 'roles', 'force_update'], 'required': ['user', 'password']},
'doc': {'function': doc, 'params': ['doc', 'collection', 'force_update'], 'required': ['doc', 'collection']},
'index': {'function': index, 'params': ['indexes', 'collection'], 'required': ['indexes', 'collection']}
}
# =========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec=dict(
connect_string=dict(required=True),
database=dict(required=True, aliases=['db']),
mode=dict(default='user', choices=['user', 'doc', 'index']),
user=dict(default=None),
password=dict(default=None, no_log=True),
roles=dict(default=None, type='list'),
collection=dict(default=None),
doc=dict(default=None, type='dict'),
force_update=dict(default=False, type='bool'),
indexes=dict(default=None, type='list'),
)
)
mode = module.params['mode']
db_name = module.params['database']
params = {key: module.params[key] for key in OPERATIONS[mode]['params']}
check_params(params, mode, module)
try:
client = MongoClient(module.params['connect_string'])
except NameError:
module.fail_json(msg='the python pymongo module is required')
except Exception as e:
module.fail_json(msg='unable to connect to database: %s' % to_native(e), exception=traceback.format_exc())
OPERATIONS[mode]['function'](module, client, db_name, **params)
if __name__ == '__main__':
main()