diff --git a/.travis.yml b/.travis.yml index 5d8055d..77a08f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,23 @@ sudo: required language: python python: - "2.7" - - "3.6" env: + - > + DISTRIBUTION=ubuntu + DIST_VERSION=16_04-builded + MONGODB_VERSION=3.6 + - > + DISTRIBUTION=ubuntu + DIST_VERSION=16_04-builded + MONGODB_VERSION=3.4 + - > + DISTRIBUTION=ubuntu + DIST_VERSION=16_04-builded + MONGODB_VERSION=3.2 + - > + DISTRIBUTION=ubuntu-upstart + DIST_VERSION=14.04 + MONGODB_VERSION=3.6 - > DISTRIBUTION=ubuntu-upstart DIST_VERSION=14.04 @@ -16,22 +31,46 @@ env: DISTRIBUTION=ubuntu-upstart DIST_VERSION=14.04 MONGODB_VERSION=3.2 + - > + DISTRIBUTION=debian + DIST_VERSION=9-builded + MONGODB_VERSION=3.2 + - > + DISTRIBUTION=debian + DIST_VERSION=8-builded + MONGODB_VERSION=3.6 + - > + DISTRIBUTION=debian + DIST_VERSION=8-builded + MONGODB_VERSION=3.4 + - > + DISTRIBUTION=debian + DIST_VERSION=8-builded + MONGODB_VERSION=3.2 - > DISTRIBUTION=centos DIST_VERSION=6-builded - MONGODB_VERSION=3.2 + MONGODB_VERSION=3.6 - > DISTRIBUTION=centos DIST_VERSION=6-builded MONGODB_VERSION=3.4 - > DISTRIBUTION=centos - DIST_VERSION=7-builded + DIST_VERSION=6-builded MONGODB_VERSION=3.2 + - > + DISTRIBUTION=centos + DIST_VERSION=7-builded + MONGODB_VERSION=3.6 - > DISTRIBUTION=centos DIST_VERSION=7-builded MONGODB_VERSION=3.4 + - > + DISTRIBUTION=centos + DIST_VERSION=7-builded + MONGODB_VERSION=3.2 # - > # distribution=debian # version=7 diff --git a/README.md b/README.md index 6071965..7242f5e 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,19 @@ Ansible role which manages [MongoDB](http://www.mongodb.org/). MongoDB support matrix: -| Distribution | MongoDB 2.4 | MongoDB 2.6 | MongoDB 3.0 | MongoDB 3.2 | MongoDB 3.4 | -| ------------ |:-----------:|:-----------:|:-----------:|:-----------:|:-----------:| -| Ubuntu 14.04 | :no_entry: | :white_check_mark: | :white_check_mark: | :white_check_mark:| :x:| -| Ubuntu 16.04 | :no_entry: | :x: | :x: | :x:| :x:| -| Debian 8.x | :no_entry: | :x: | :x: | :x:| :x:| -| Debian 9.x | :no_entry: | :x: | :x: | :x:| :x:| -| RHEL 6.x | :no_entry: | :interrobang: | :interrobang: | :interrobang: | :interrobang: | -| RHEL 7.x | :no_entry: | :interrobang: | :interrobang: | :interrobang: | :interrobang: | +| Distribution | MongoDB 2.4 | MongoDB 2.6 | MongoDB 3.0 | MongoDB 3.2 | MongoDB 3.4 | MongoDB 3.6 | +| ------------ |:-----------:|:-----------:|:-----------:|:-----------:|:-----------:|:-----------:| +| Ubuntu 14.04 | :no_entry: | :no_entry: | :no_entry: | :white_check_mark:| :white_check_mark:| :white_check_mark:| +| Ubuntu 16.04 | :no_entry: | :no_entry: | :no_entry: | :white_check_mark:| :white_check_mark:| :white_check_mark:| +| Debian 8.x | :no_entry: | :no_entry: | :no_entry: | :white_check_mark:| :white_check_mark:| :white_check_mark:| +| Debian 9.x | :no_entry: | :no_entry: | :no_entry: | :white_check_mark:| :x:| :x:| +| RHEL 6.x | :no_entry: | :no_entry: | :no_entry: | :white_check_mark: | :white_check_mark: | :white_check_mark:| +| RHEL 7.x | :no_entry: | :no_entry: | :no_entry: | :white_check_mark: | :white_check_mark: | :white_check_mark:| -:white_check_mark: - fully tested, should work fine -:interrobang: - will be added testing suite soon -:x: - don't have official support -:no_entry: - does't have support, because used old format of configuration files +- :white_check_mark: - fully tested, should works fine +- :interrobang: - maybe works, not tested +- :x: - don't have official support +- :no_entry: - MongoDB has reached EOL #### Variables @@ -33,9 +33,9 @@ MongoDB support matrix: mongodb_package: mongodb-org # You can control installed version via this param. -# Should be '2.6', '3.0', '3.2' or '3.4'. This role does't support MongoDB < 2.4. +# Should be '3.2', '3.4', '3.6'. This role doesn't support MongoDB < 3.2. # I will recommend you to use latest version of MongoDB. -mongodb_version: "3.4" +mongodb_version: "3.6" mongodb_force_wait_for_port: false # When not forced, the role will wait for mongod port to become available only with systemd mongodb_pymongo_from_pip: true # Install latest PyMongo via PIP or package manager diff --git a/library/mongodb_replication.py b/library/mongodb_replication.py index efb4a1f..edc2253 100644 --- a/library/mongodb_replication.py +++ b/library/mongodb_replication.py @@ -45,6 +45,11 @@ options: - The port to connect to required: false default: 27017 + login_database: + description: + - The database where login credentials are stored + required: false + default: admin replica_set: description: - Replica set to connect to (automatically connects to primary for writes) @@ -69,6 +74,12 @@ options: description: - Whether to use an SSL connection when connecting to the database default: False + ssl_cert_reqs: + description: + - Specifies whether a certificate is required from the other side of the connection, and whether it will be validated if provided. + required: false + default: "CERT_REQUIRED" + choices: ["CERT_REQUIRED", "CERT_OPTIONAL", "CERT_NONE"] build_indexes: description: - Determines whether the mongod builds indexes on this member. @@ -146,7 +157,9 @@ host_type: sample: "replica" ''' import ConfigParser +import ssl as ssl_lib import time +from datetime import datetime as dtdatetime from distutils.version import LooseVersion try: from pymongo.errors import ConnectionFailure @@ -156,7 +169,6 @@ try: from pymongo.errors import ServerSelectionTimeoutError from pymongo import version as PyMongoVersion from pymongo import MongoClient - from pymongo import MongoReplicaSetClient except ImportError: pymongo_found = False else: @@ -203,6 +215,7 @@ def check_members(state, module, client, host_name, host_port, host_type): module.exit_json(changed=False, host_name=host_name, host_port=host_port, host_type=host_type) def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwargs): + start_time = dtdatetime.now() while True: try: admin_db = client['admin'] @@ -240,12 +253,12 @@ def add_host(module, client, host_name, host_port, host_type, timeout=180, **kwa admin_db.command('replSetReconfig', cfg) return except (OperationFailure, AutoReconnect) as e: - timeout = timeout - 5 - if timeout <= 0: + if (dtdatetime.now() - start_time).seconds > timeout: module.fail_json(msg='reached timeout while waiting for rs.reconfig(): %s' % str(e)) time.sleep(5) def remove_host(module, client, host_name, timeout=180): + start_time = dtdatetime.now() while True: try: admin_db = client['admin'] @@ -269,8 +282,7 @@ def remove_host(module, client, host_name, timeout=180): fail_msg = "couldn't find member with hostname: {0} in replica set members list".format(host_name) module.fail_json(msg=fail_msg) except (OperationFailure, AutoReconnect) as e: - timeout = timeout - 5 - if timeout <= 0: + if (dtdatetime.now() - start_time).seconds > timeout: module.fail_json(msg='reached timeout while waiting for rs.reconfig(): %s' % str(e)) time.sleep(5) @@ -289,14 +301,23 @@ def load_mongocnf(): return creds -def wait_for_ok_and_master(module, client, timeout = 60): +def wait_for_ok_and_master(module, connection_params, timeout = 180): + start_time = dtdatetime.now() while True: - status = client.admin.command('replSetGetStatus', check=False) - if status['ok'] == 1 and status['myState'] == 1: - return + try: + client = MongoClient(**connection_params) + authenticate(client, connection_params["username"], connection_params["password"]) - timeout = timeout - 1 - if timeout == 0: + status = client.admin.command('replSetGetStatus', check=False) + if status['ok'] == 1 and status['myState'] == 1: + return + + except ServerSelectionTimeoutError: + pass + + client.close() + + if (dtdatetime.now() - start_time).seconds > timeout: module.fail_json(msg='reached timeout while waiting for rs.status() to become ok=1') time.sleep(1) @@ -324,11 +345,13 @@ def main(): login_password=dict(default=None, no_log=True), login_host=dict(default='localhost'), login_port=dict(default='27017'), + login_database=dict(default="admin"), replica_set=dict(default=None), host_name=dict(default='localhost'), host_port=dict(default='27017'), host_type=dict(default='replica', choices=['replica','arbiter']), - ssl=dict(default='false'), + ssl=dict(default=False, type='bool'), + ssl_cert_reqs=dict(default='CERT_REQUIRED', choices=['CERT_NONE', 'CERT_OPTIONAL', 'CERT_REQUIRED']), build_indexes = dict(type='bool', default='yes'), hidden = dict(type='bool', default='no'), priority = dict(default='1.0'), @@ -339,12 +362,13 @@ def main(): ) if not pymongo_found: - module.fail_json(msg='the python pymongo (>= 2.4) module is required') + module.fail_json(msg='the python pymongo (>= 3.2) module is required') login_user = module.params['login_user'] login_password = module.params['login_password'] login_host = module.params['login_host'] login_port = module.params['login_port'] + login_database = module.params['login_database'] replica_set = module.params['replica_set'] host_name = module.params['host_name'] host_port = module.params['host_port'] @@ -359,22 +383,48 @@ def main(): if replica_set is None: module.fail_json(msg='replica_set parameter is required') else: - client = MongoClient(login_host, int(login_port), replicaSet=replica_set, - ssl=ssl, serverSelectionTimeoutMS=5000) + connection_params = { + "host": login_host, + "port": int(login_port), + "username": login_user, + "password": login_password, + "authsource": login_database, + "serverselectiontimeoutms": 5000, + "replicaset": replica_set, + } + if ssl: + connection_params["ssl"] = ssl + connection_params["ssl_cert_reqs"] = getattr(ssl_lib, module.params['ssl_cert_reqs']) + + client = MongoClient(**connection_params) authenticate(client, login_user, login_password) client['admin'].command('replSetGetStatus') except ServerSelectionTimeoutError: try: - client = MongoClient(login_host, int(login_port), ssl=ssl) + connection_params = { + "host": login_host, + "port": int(login_port), + "username": login_user, + "password": login_password, + "authsource": login_database, + "serverselectiontimeoutms": 10000, + } + + if ssl: + connection_params["ssl"] = ssl + connection_params["ssl_cert_reqs"] = getattr(ssl_lib, module.params['ssl_cert_reqs']) + + client = MongoClient(**connection_params) authenticate(client, login_user, login_password) if state == 'present': new_host = { '_id': 0, 'host': "{0}:{1}".format(host_name, host_port) } if priority != 1.0: new_host['priority'] = priority config = { '_id': "{0}".format(replica_set), 'members': [new_host] } client['admin'].command('replSetInitiate', config) - wait_for_ok_and_master(module, client) + client.close() + wait_for_ok_and_master(module, connection_params) replica_set_created = True module.exit_json(changed=True, host_name=host_name, host_port=host_port, host_type=host_type) except OperationFailure as e: @@ -382,6 +432,9 @@ def main(): except ConnectionFailure as e: module.fail_json(msg='unable to connect to database: %s' % str(e)) + # reconnect again + client = MongoClient(**connection_params) + authenticate(client, login_user, login_password) check_compatibility(module, client) check_members(state, module, client, host_name, host_port, host_type) diff --git a/library/mongodb_user_fixed.py b/library/mongodb_user_fixed.py new file mode 100644 index 0000000..3c04724 --- /dev/null +++ b/library/mongodb_user_fixed.py @@ -0,0 +1,462 @@ +#!/usr/bin/python + +# (c) 2012, Elliott Foster +# Sponsored by Four Kitchens http://fourkitchens.com. +# (c) 2014, Epic Games, Inc. +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: mongodb_user +short_description: Adds or removes a user from a MongoDB database. +description: + - Adds or removes a user from a MongoDB database. +version_added: "1.1" +options: + login_user: + description: + - The username used to authenticate with + required: false + default: null + login_password: + description: + - The password used to authenticate with + required: false + default: null + login_host: + description: + - The host running the database + required: false + default: localhost + login_port: + description: + - The port to connect to + required: false + default: 27017 + login_database: + version_added: "2.0" + description: + - The database where login credentials are stored + required: false + default: admin + replica_set: + version_added: "1.6" + description: + - Replica set to connect to (automatically connects to primary for writes) + required: false + default: null + database: + description: + - The name of the database to add/remove the user from + required: true + name: + description: + - The name of the user to add or remove + required: true + default: null + aliases: [ 'user' ] + password: + description: + - The password to use for the user + required: false + default: null + ssl: + version_added: "1.8" + description: + - Whether to use an SSL connection when connecting to the database + default: False + ssl_cert_reqs: + version_added: "2.2" + description: + - Specifies whether a certificate is required from the other side of the connection, and whether it will be validated if provided. + required: false + default: "CERT_REQUIRED" + choices: ["CERT_REQUIRED", "CERT_OPTIONAL", "CERT_NONE"] + roles: + version_added: "1.3" + description: + - > + The database user roles valid values could either be one or more of the following strings: + 'read', 'readWrite', 'dbAdmin', 'userAdmin', 'clusterAdmin', 'readAnyDatabase', 'readWriteAnyDatabase', 'userAdminAnyDatabase', + 'dbAdminAnyDatabase' + - "Or the following dictionary '{ db: DATABASE_NAME, role: ROLE_NAME }'." + - "This param requires pymongo 2.5+. If it is a string, mongodb 2.4+ is also required. If it is a dictionary, mongo 2.6+ is required." + required: false + default: "readWrite" + state: + description: + - The database user state + required: false + default: present + choices: [ "present", "absent" ] + update_password: + required: false + default: always + choices: ['always', 'on_create'] + version_added: "2.1" + description: + - C(always) will update passwords if they differ. C(on_create) will only set the password for newly created users. + +notes: + - Requires the pymongo Python package on the remote host, version 2.4.2+. This + can be installed using pip or the OS package manager. @see http://api.mongodb.org/python/current/installation.html +requirements: [ "pymongo" ] +author: + - "Elliott Foster (@elliotttf)" + - "Julien Thebault (@lujeni)" +''' + +EXAMPLES = ''' +# Create 'burgers' database user with name 'bob' and password '12345'. +- mongodb_user: + database: burgers + name: bob + password: 12345 + state: present + +# Create a database user via SSL (MongoDB must be compiled with the SSL option and configured properly) +- mongodb_user: + database: burgers + name: bob + password: 12345 + state: present + ssl: True + +# Delete 'burgers' database user with name 'bob'. +- mongodb_user: + database: burgers + name: bob + state: absent + +# Define more users with various specific roles (if not defined, no roles is assigned, and the user will be added via pre mongo 2.2 style) +- mongodb_user: + database: burgers + name: ben + password: 12345 + roles: read + state: present +- mongodb_user: + database: burgers + name: jim + password: 12345 + roles: readWrite,dbAdmin,userAdmin + state: present +- mongodb_user: + database: burgers + name: joe + password: 12345 + roles: readWriteAnyDatabase + state: present + +# add a user to database in a replica set, the primary server is automatically discovered and written to +- mongodb_user: + database: burgers + name: bob + replica_set: belcher + password: 12345 + roles: readWriteAnyDatabase + state: present + +# add a user 'oplog_reader' with read only access to the 'local' database on the replica_set 'belcher'. This is useful for oplog access (MONGO_OPLOG_URL). +# please notice the credentials must be added to the 'admin' database because the 'local' database is not syncronized and can't receive user credentials +# To login with such user, the connection string should be MONGO_OPLOG_URL="mongodb://oplog_reader:oplog_reader_password@server1,server2/local?authSource=admin" +# This syntax requires mongodb 2.6+ and pymongo 2.5+ +- mongodb_user: + login_user: root + login_password: root_password + database: admin + user: oplog_reader + password: oplog_reader_password + state: present + replica_set: belcher + roles: + - db: local + role: read + +''' + +RETURN = ''' +user: + description: The name of the user to add or remove. + returned: success + type: string +''' + +import os +import ssl as ssl_lib +import traceback +from distutils.version import LooseVersion + +try: + from pymongo.errors import ConnectionFailure + from pymongo.errors import OperationFailure + from pymongo import version as PyMongoVersion + from pymongo import MongoClient +except ImportError: + try: # for older PyMongo 2.2 + from pymongo import Connection as MongoClient + except ImportError: + pymongo_found = False + else: + pymongo_found = True +else: + pymongo_found = True + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils.six.moves import configparser +from ansible.module_utils._text import to_native + + +# ========================================= +# MongoDB module specific support methods. +# + +def check_compatibility(module, client): + """Check the compatibility between the driver and the database. + + See: https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#python-driver-compatibility + + Args: + module: Ansible module. + client (cursor): Mongodb cursor on admin database. + """ + loose_srv_version = LooseVersion(client.server_info()['version']) + loose_driver_version = LooseVersion(PyMongoVersion) + + if loose_srv_version >= LooseVersion('3.2') and loose_driver_version < LooseVersion('3.2'): + module.fail_json(msg=' (Note: you must use pymongo 3.2+ with MongoDB >= 3.2)') + + elif loose_srv_version >= LooseVersion('3.0') and loose_driver_version <= LooseVersion('2.8'): + module.fail_json(msg=' (Note: you must use pymongo 2.8+ with MongoDB 3.0)') + + elif loose_srv_version >= LooseVersion('2.6') and loose_driver_version <= LooseVersion('2.7'): + module.fail_json(msg=' (Note: you must use pymongo 2.7+ with MongoDB 2.6)') + + elif LooseVersion(PyMongoVersion) <= LooseVersion('2.5'): + module.fail_json(msg=' (Note: you must be on mongodb 2.4+ and pymongo 2.5+ to use the roles param)') + + +def user_find(client, user, db_name): + """Check if the user exists. + + Args: + client (cursor): Mongodb cursor on admin database. + user (str): User to check. + db_name (str): User's database. + + Returns: + dict: when user exists, False otherwise. + """ + for mongo_user in client["admin"].system.users.find(): + if mongo_user['user'] == user: + # NOTE: there is no 'db' field in mongo 2.4. + if 'db' not in mongo_user: + return mongo_user + + if mongo_user["db"] == db_name: + return mongo_user + return False + + +def user_add(module, client, db_name, user, password, roles): + # pymongo's user_add is a _create_or_update_user so we won't know if it was changed or updated + # without reproducing a lot of the logic in database.py of pymongo + db = client[db_name] + + if roles is None: + db.add_user(user, password, False) + else: + db.add_user(user, password, None, roles=roles) + + +def user_remove(module, client, db_name, user): + exists = user_find(client, user, db_name) + if exists: + if module.check_mode: + module.exit_json(changed=True, user=user) + db = client[db_name] + db.remove_user(user) + else: + module.exit_json(changed=False, user=user) + + +def load_mongocnf(): + config = configparser.RawConfigParser() + mongocnf = os.path.expanduser('~/.mongodb.cnf') + + try: + config.readfp(open(mongocnf)) + creds = dict( + user=config.get('client', 'user'), + password=config.get('client', 'pass') + ) + except (configparser.NoOptionError, IOError): + return False + + return creds + + +def check_if_roles_changed(uinfo, roles, db_name): + # We must be aware of users which can read the oplog on a replicaset + # Such users must have access to the local DB, but since this DB does not store users credentials + # and is not synchronized among replica sets, the user must be stored on the admin db + # Therefore their structure is the following : + # { + # "_id" : "admin.oplog_reader", + # "user" : "oplog_reader", + # "db" : "admin", # <-- admin DB + # "roles" : [ + # { + # "role" : "read", + # "db" : "local" # <-- local DB + # } + # ] + # } + + def make_sure_roles_are_a_list_of_dict(roles, db_name): + output = list() + for role in roles: + if isinstance(role, (binary_type, text_type)): + new_role = {"role": role, "db": db_name} + output.append(new_role) + else: + output.append(role) + return output + + roles_as_list_of_dict = make_sure_roles_are_a_list_of_dict(roles, db_name) + uinfo_roles = uinfo.get('roles', []) + + if sorted(roles_as_list_of_dict) == sorted(uinfo_roles): + return False + return True + + +# ========================================= +# Module execution. +# + +def main(): + module = AnsibleModule( + argument_spec=dict( + login_user=dict(default=None), + login_password=dict(default=None, no_log=True), + login_host=dict(default='localhost'), + login_port=dict(default='27017'), + login_database=dict(default="admin"), + replica_set=dict(default=None), + database=dict(required=True, aliases=['db']), + name=dict(required=True, aliases=['user']), + password=dict(aliases=['pass'], no_log=True), + ssl=dict(default=False, type='bool'), + roles=dict(default=None, type='list'), + state=dict(default='present', choices=['absent', 'present']), + update_password=dict(default="always", choices=["always", "on_create"]), + ssl_cert_reqs=dict(default='CERT_REQUIRED', choices=['CERT_NONE', 'CERT_OPTIONAL', 'CERT_REQUIRED']), + ), + supports_check_mode=True + ) + + if not pymongo_found: + module.fail_json(msg='the python pymongo module is required') + + login_user = module.params['login_user'] + login_password = module.params['login_password'] + login_host = module.params['login_host'] + login_port = module.params['login_port'] + login_database = module.params['login_database'] + + replica_set = module.params['replica_set'] + db_name = module.params['database'] + user = module.params['name'] + password = module.params['password'] + ssl = module.params['ssl'] + roles = module.params['roles'] or [] + state = module.params['state'] + update_password = module.params['update_password'] + + try: + connection_params = { + "host": login_host, + "port": int(login_port), + "username": login_user, + "password": login_password, + "authSource": login_database, + } + + if replica_set: + connection_params["replicaset"] = replica_set + + if ssl: + connection_params["ssl"] = ssl + connection_params["ssl_cert_reqs"] = getattr(ssl_lib, module.params['ssl_cert_reqs']) + + client = MongoClient(**connection_params) + + # NOTE: this check must be done ASAP. + # We doesn't need to be authenticated. + check_compatibility(module, client) + + if login_user is None and login_password is None: + mongocnf_creds = load_mongocnf() + if mongocnf_creds is not False: + login_user = mongocnf_creds['user'] + login_password = mongocnf_creds['password'] + elif login_password is None or login_user is None: + module.fail_json(msg='when supplying login arguments, both login_user and login_password must be provided') + + if login_user is not None and login_password is not None: + client.admin.authenticate(login_user, login_password, source=login_database) + elif LooseVersion(PyMongoVersion) >= LooseVersion('3.0'): + if db_name != "admin": + module.fail_json(msg='The localhost login exception only allows the first admin account to be created') + # else: this has to be the first admin user added + + except Exception as e: + module.fail_json(msg='unable to connect to database: %s' % to_native(e), exception=traceback.format_exc()) + + if state == 'present': + if password is None and update_password == 'always': + module.fail_json(msg='password parameter required when adding a user unless update_password is set to on_create') + + try: + if update_password != 'always': + uinfo = user_find(client, user, db_name) + if uinfo: + password = None + if not check_if_roles_changed(uinfo, roles, db_name): + module.exit_json(changed=False, user=user) + + if module.check_mode: + module.exit_json(changed=True, user=user) + + user_add(module, client, db_name, user, password, roles) + except Exception as e: + module.fail_json(msg='Unable to add or update user: %s' % to_native(e), exception=traceback.format_exc()) + + # Here we can check password change if mongo provide a query for that : https://jira.mongodb.org/browse/SERVER-22848 + # newuinfo = user_find(client, user, db_name) + # if uinfo['role'] == newuinfo['role'] and CheckPasswordHere: + # module.exit_json(changed=False, user=user) + + elif state == 'absent': + try: + user_remove(module, client, db_name, user) + except Exception as e: + module.fail_json(msg='Unable to remove user: %s' % to_native(e), exception=traceback.format_exc()) + + module.exit_json(changed=True, user=user) + + +if __name__ == '__main__': + main() diff --git a/meta/main.yml b/meta/main.yml index ab3dd94..cb12430 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -11,11 +11,12 @@ galaxy_info: platforms: - name: Ubuntu versions: - - precise - trusty + - xenial - name: Debian versions: - - wheezy + - jessie + - stretch - name: EL versions: - 6 diff --git a/tasks/auth_initialization.yml b/tasks/auth_initialization.yml index de7fcdf..c5c5b79 100644 --- a/tasks/auth_initialization.yml +++ b/tasks/auth_initialization.yml @@ -1,5 +1,5 @@ --- -- name: Move back mongod.conf +- name: Use different mongod.conf for auth initialization template: src=mongod_init.conf.j2 dest=/etc/mongod.conf owner=root group=root mode=0644 - name: Restart mongodb service @@ -77,7 +77,3 @@ - name: Wait MongoDB port is listening wait_for: host="{{ item }}" port="{{ mongodb_net_port }}" delay=5 state=started with_items: "{{ mongodb_net_bindip.split(',') | map('replace', '0.0.0.0', '127.0.0.1') | list }}" - -- name: stop mongodb if was not started - shell: "kill {{ pidof_mongod.stdout }}" - when: mongodb_manage_service == false and pidof_mongod.rc == 0 diff --git a/tasks/install.debian.yml b/tasks/install.debian.yml index 04f9853..5423040 100644 --- a/tasks/install.debian.yml +++ b/tasks/install.debian.yml @@ -11,17 +11,10 @@ mongodb_is_systemd: "{{ sbin_init.stat.islnk is defined and sbin_init.stat.islnk }}" mongodb_major_version: "{{ mongodb_version[0:3] }}" -- name: Add systemd configuration if present - copy: src=mongodb.service dest=/lib/systemd/system/mongodb.service owner=root group=root mode=0640 - when: mongodb_is_systemd - -- name: Add symlink for systemd - file: src=/lib/systemd/system/mongodb.service dest=/etc/systemd/system/multi-user.target.wants/mongodb.service state=link - when: mongodb_is_systemd - notify: reload systemd - -- meta: flush_handlers - when: mongodb_is_systemd +- name: Overwrite mongodb_version especially for Debian 9 + set_fact: + mongodb_major_version: "3.2" + when: ansible_distribution_release == "stretch" - name: Add APT key apt_key: @@ -47,9 +40,20 @@ - "{{mongodb_package}}" - numactl +- name: Add systemd configuration if present + copy: src=mongodb.service dest=/lib/systemd/system/mongodb.service owner=root group=root mode=0640 + when: mongodb_is_systemd + +- name: Add symlink for systemd + file: src=/lib/systemd/system/mongodb.service dest=/etc/systemd/system/multi-user.target.wants/mongodb.service state=link + when: mongodb_is_systemd + notify: reload systemd + - name: reload systemd shell: systemctl daemon-reload - changed_when: false + when: mongodb_is_systemd and mongodb_manage_service + +- meta: flush_handlers when: mongodb_is_systemd - name: Install PyMongo package diff --git a/tasks/main.yml b/tasks/main.yml index 94d0c8d..01bba78 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -3,19 +3,20 @@ - name: Include OS-specific variables include_vars: "{{ item }}" with_first_found: + - "{{ ansible_distribution_release }}.yml" - "{{ ansible_distribution }}.yml" - "{{ ansible_os_family }}.yml" -- name: Include installation on Debian-based OS - include_tasks: "install.{{ ansible_os_family | lower }}.yml" +- name: Include installation tasks + include: "install.{{ ansible_os_family | lower }}.yml" tags: [mongodb] - name: Include configuration.yml - include_tasks: configure.yml + include: configure.yml tags: [mongodb] - name: Include replication and auth configuration - include_tasks: replication_init_auth.yml + include: replication_init_auth.yml when: ( mongodb_replication_replset and mongodb_replication_replset != '' and mongodb_security_authorization == 'enabled' @@ -23,7 +24,7 @@ tags: [mongodb] - name: Include replication configuration - include_tasks: replication.yml + include: replication.yml when: mongodb_replication_replset and mongodb_replication_replset != '' tags: [mongodb] @@ -42,7 +43,7 @@ tags: [mongodb] - name: Include authorization configuration - include_tasks: auth_initialization.yml + include: auth_initialization.yml when: ( mongodb_security_authorization == 'enabled' and (not mongodb_replication_replset or mongodb_replication_replset == '') @@ -50,7 +51,7 @@ tags: [mongodb] - name: create normal users with replicaset - mongodb_user: + mongodb_user_fixed: database: "{{ item.database }}" name: "{{ item.name }}" password: "{{ item.password }}" @@ -70,7 +71,7 @@ tags: [mongodb] - name: create normal users without replicaset - mongodb_user: + mongodb_user_fixed: database: "{{ item.database }}" name: "{{ item.name }}" password: "{{ item.password }}" @@ -88,6 +89,6 @@ tags: [mongodb] - name: Include MMS Agent configuration - include_tasks: mms-agent.yml + include: mms-agent.yml when: mongodb_mms_api_key != "" tags: [mongodb] diff --git a/tasks/replication_init_auth.yml b/tasks/replication_init_auth.yml index 933d957..4d75642 100644 --- a/tasks/replication_init_auth.yml +++ b/tasks/replication_init_auth.yml @@ -16,7 +16,7 @@ register: mongodb_replica_init ignore_errors: true -- include_tasks: auth_initialization.yml +- include: auth_initialization.yml when: mongodb_replica_init|failed - name: Replication configuration | 2nd Pt diff --git a/templates/mongod.conf.j2 b/templates/mongod.conf.j2 index ba3ee92..627a830 100644 --- a/templates/mongod.conf.j2 +++ b/templates/mongod.conf.j2 @@ -2,8 +2,10 @@ net: bindIp: {{ mongodb_net_bindip }} + {% if mongodb_major_version | version_compare("3.6", "<") -%} http: enabled: {{ mongodb_net_http_enabled | to_nice_json }} + {% endif -%} ipv6: {{ mongodb_net_ipv6 | to_nice_json }} maxIncomingConnections: {{ mongodb_net_maxconns }} port: {{ mongodb_net_port }} @@ -31,7 +33,7 @@ security: storage: dbPath: {{ mongodb_storage_dbpath }} - {% if mongodb_major_version|float >= 3.0 -%} + {% if mongodb_major_version | version_compare("3.0", ">=") -%} engine: {{ mongodb_storage_engine }} {% endif -%} journal: @@ -42,13 +44,6 @@ storage: enforced: {{ mongodb_storage_quota_enforced | to_nice_json }} maxFilesPerDB: {{ mongodb_storage_quota_maxfiles }} smallFiles: {{ mongodb_storage_smallfiles | to_nice_json }} - {% endif -%} - {% if mongodb_major_version == '2.6' -%} - quota: - enforced: {{ mongodb_storage_quota_enforced | to_nice_json }} - maxFilesPerDB: {{ mongodb_storage_quota_maxfiles }} - preallocDataFiles: {{ mongodb_storage_prealloc | to_nice_json }} - smallFiles: {{ mongodb_storage_smallfiles | to_nice_json }} {% endif %} systemLog: diff --git a/templates/mongod_init.conf.j2 b/templates/mongod_init.conf.j2 index 2774560..314dd32 100644 --- a/templates/mongod_init.conf.j2 +++ b/templates/mongod_init.conf.j2 @@ -2,8 +2,10 @@ net: bindIp: '127.0.0.1' + {% if mongodb_major_version | version_compare("3.6", "<") -%} http: enabled: {{ mongodb_net_http_enabled | to_nice_json }} + {% endif -%} ipv6: {{ mongodb_net_ipv6 | to_nice_json }} maxIncomingConnections: {{ mongodb_net_maxconns }} port: {{ mongodb_net_port }} @@ -13,13 +15,13 @@ processManagement: {% if mongodb_pidfile_path is defined and mongodb_pidfile_path != '' -%} pidFilePath: {{ mongodb_pidfile_path }} {% endif %} - + security: authorization: 'disabled' storage: dbPath: {{ mongodb_storage_dbpath }} - {% if mongodb_major_version|float >= 3.0 -%} + {% if mongodb_major_version | version_compare("3.0", ">=") -%} engine: {{ mongodb_storage_engine }} {% endif -%} journal: @@ -30,13 +32,6 @@ storage: enforced: {{ mongodb_storage_quota_enforced | to_nice_json }} maxFilesPerDB: {{ mongodb_storage_quota_maxfiles }} smallFiles: {{ mongodb_storage_smallfiles | to_nice_json }} - {% endif -%} - {% if mongodb_major_version == '2.6' -%} - quota: - enforced: {{ mongodb_storage_quota_enforced | to_nice_json }} - maxFilesPerDB: {{ mongodb_storage_quota_maxfiles }} - preallocDataFiles: {{ mongodb_storage_prealloc | to_nice_json }} - smallFiles: {{ mongodb_storage_smallfiles | to_nice_json }} {% endif %} systemLog: diff --git a/tests/Dockerfile.debian_8-builded b/tests/Dockerfile.debian_8-builded new file mode 100644 index 0000000..4055247 --- /dev/null +++ b/tests/Dockerfile.debian_8-builded @@ -0,0 +1,7 @@ +FROM debian:8 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update && \ + apt install --yes python-minimal && \ + rm /lib/systemd/system/getty@.service diff --git a/tests/Dockerfile.debian_9-builded b/tests/Dockerfile.debian_9-builded new file mode 100644 index 0000000..8397f5c --- /dev/null +++ b/tests/Dockerfile.debian_9-builded @@ -0,0 +1,31 @@ +FROM debian:9 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update && \ + apt install --yes python-minimal systemd gnupg + +RUN cd /lib/systemd/system/sysinit.target.wants/ && \ + ls | grep -v systemd-tmpfiles-setup.service | xargs rm -f && \ + rm -f /lib/systemd/system/sockets.target.wants/*udev* && \ + systemctl mask -- \ + tmp.mount \ + etc-hostname.mount \ + etc-hosts.mount \ + etc-resolv.conf.mount \ + -.mount \ + swap.target \ + getty.target \ + getty-static.service \ + dev-mqueue.mount \ + cgproxy.service \ + systemd-tmpfiles-setup-dev.service \ + systemd-remount-fs.service \ + systemd-ask-password-wall.path \ + systemd-logind.service && \ + systemctl set-default multi-user.target || true + +RUN sed -ri /etc/systemd/journald.conf \ + -e 's!^#?Storage=.*!Storage=volatile!' + +RUN ln -s /lib/systemd/systemd /sbin/init diff --git a/tests/Dockerfile.ubuntu_16_04-builded b/tests/Dockerfile.ubuntu_16_04-builded new file mode 100644 index 0000000..953e907 --- /dev/null +++ b/tests/Dockerfile.ubuntu_16_04-builded @@ -0,0 +1,7 @@ +FROM ubuntu:16.04 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update && \ + apt install --yes python-minimal iproute2 && \ + rm /lib/systemd/system/getty@.service diff --git a/tests/group_vars/all.yml b/tests/group_vars/all.yml index f588acf..09e5b8c 100644 --- a/tests/group_vars/all.yml +++ b/tests/group_vars/all.yml @@ -1,7 +1,7 @@ --- image_name: "ubuntu-upstart:14.04" -mongodb_version: "3.0" +mongodb_version: "3.6" mongodb_storage_dbpath: /var/lib/mongodb mongodb_net_bindip: 0.0.0.0 mongodb_login_host: "{{ hostvars[groups['mongo_master'][0]].ansible_default_ipv4.address }}" diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index 4e5c11e..e5f1bdf 100644 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -2,7 +2,7 @@ # -*- mode: sh; -*- # File: test.sh -# Time-stamp: <2018-02-15 17:02:22> +# Time-stamp: <2018-02-26 16:04:24> # Copyright (C) 2018 Sergei Antipov # Description: @@ -11,6 +11,7 @@ set -o nounset set -o errexit # Test 1 +echo "ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e mongodb_version=${MONGODB_VERSION} -e image_name=${DISTRIBUTION}:${DIST_VERSION}" ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e mongodb_version=${MONGODB_VERSION} -e image_name=${DISTRIBUTION}:${DIST_VERSION} # Idempotence test ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e mongodb_version=${MONGODB_VERSION} -e image_name=${DISTRIBUTION}:${DIST_VERSION} | \ @@ -20,6 +21,7 @@ ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e mongodb_versi docker kill mongo{1,2,3} && docker rm mongo{1,2,3} # Test 2 +echo "ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_security_authorization='enabled'" ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_security_authorization='enabled' # Idempotence test ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_security_authorization='enabled' \ @@ -29,6 +31,7 @@ ansible-playbook -i tests/hosts tests/site.yml -e target=mongo1 -e image_name=${ docker kill mongo{1,2,3} && docker rm mongo{1,2,3} # Test 3 +echo "ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_replication_replset='testrs'" ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_replication_replset='testrs' # Idempotence test ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_replication_replset='testrs' \ @@ -38,6 +41,7 @@ ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${D docker kill mongo{1,2,3} && docker rm mongo{1,2,3} # Test 4 +echo "ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_replication_replset='testrs' -e mongodb_security_authorization='enabled'" ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_replication_replset='testrs' -e mongodb_security_authorization='enabled' # Idempotence test ansible-playbook -i tests/hosts tests/site.yml -e target=mongo -e image_name=${DISTRIBUTION}:${DIST_VERSION} -e mongodb_version=${MONGODB_VERSION} -e mongodb_replication_replset='testrs' -e mongodb_security_authorization='enabled' \ diff --git a/tests/site.yml b/tests/site.yml index 6c08bde..204851f 100644 --- a/tests/site.yml +++ b/tests/site.yml @@ -20,7 +20,6 @@ - hosts: "{{ target }}" become: no gather_facts: yes - roles: - role: greendayonfire.mongodb when: "'mongo_master' in group_names" diff --git a/vars/stretch.yml b/vars/stretch.yml new file mode 100644 index 0000000..82f0479 --- /dev/null +++ b/vars/stretch.yml @@ -0,0 +1,3 @@ +--- + +mongodb_package: mongodb