| #!/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() |