diff --git a/handlers/main.yml b/handlers/main.yml index d2861e2..d85aa03 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -19,3 +19,22 @@ - name: restart sysfsutils service: name=sysfsutils state=restarted + +- name: service started + service: + name: "{{ mongodb_daemon_name }}" + state: started + +- name: wait when mongodb is started + wait_for: + host: "{{ item }}" + port: "{{ mongodb_net_port }}" + timeout: 120 + with_items: "{{ mongodb_net_bindip.split(',') | map('replace', '0.0.0.0', '127.0.0.1') | list }}" + +- name: wait when mongodb is started on localhost + wait_for: + host: "127.0.0.1" + port: "{{ mongodb_net_port }}" + delay: 5 + timeout: 120 diff --git a/library/mongodb_user_fixed.py b/library/mongodb_user_fixed.py deleted file mode 100644 index 3c04724..0000000 --- a/library/mongodb_user_fixed.py +++ /dev/null @@ -1,462 +0,0 @@ -#!/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 5d9aab4..94bbca0 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -6,7 +6,7 @@ galaxy_info: company: Self-employed description: Manage MongoDB with authentication and replica sets license: GPLv2 - min_ansible_version: 2.4 + min_ansible_version: 2.5 platforms: - name: Ubuntu versions: diff --git a/tasks/auth_initialization.yml b/tasks/auth_initialization.yml index 1be214c..e5e40cc 100644 --- a/tasks/auth_initialization.yml +++ b/tasks/auth_initialization.yml @@ -1,13 +1,17 @@ --- - name: Use different mongod.conf for auth initialization - template: src=mongod_init.conf.j2 dest=/etc/mongod.conf owner=root group=root mode=0644 + template: + src: mongod_init.conf.j2 + dest: /etc/mongod.conf + owner: root + group: root + mode: 0644 + notify: + - mongodb restart + - wait when mongodb is started on localhost -- name: Restart mongodb service - service: name={{ mongodb_daemon_name }} state=restarted - when: mongodb_manage_service - -- name: wait MongoDB port is listening - wait_for: host=127.0.0.1 port="{{ mongodb_net_port }}" delay=5 state=started +- name: Flush all handlers at this point + meta: flush_handlers - name: create administrative user siteUserAdmin mongodb_user: @@ -58,12 +62,15 @@ no_log: true - name: Move back mongod.conf - template: src=mongod.conf.j2 dest=/etc/mongod.conf owner=root group=root mode=0644 + template: + src: mongod.conf.j2 + dest: /etc/mongod.conf + owner: root + group: root + mode: 0644 + notify: + - mongodb restart + - wait when mongodb is started -- name: Restart mongodb service - service: name={{ mongodb_daemon_name }} state=restarted - when: mongodb_manage_service - -- 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: Flush all handlers at this point + meta: flush_handlers diff --git a/tasks/configure.yml b/tasks/configure.yml index a8ae0b8..e16cf88 100644 --- a/tasks/configure.yml +++ b/tasks/configure.yml @@ -7,7 +7,7 @@ owner: "{{ mongodb_user }}" group: "root" mode: 0600 - when: mongodb_replication_replset + when: mongodb_replication_replset|default("") != "" - name: set mongodb gid group: @@ -68,18 +68,10 @@ owner: root group: root mode: 0644 - register: config_result + notify: + - mongodb restart + - service started + - wait when mongodb is started -- name: mongodb restart - service: name={{ mongodb_daemon_name }} state=restarted - when: config_result is changed and mongodb_manage_service - -- name: Ensure service is started - service: name={{ mongodb_daemon_name }} state=started - -- name: Wait when mongodb is started - wait_for: - host: "{{ item }}" - port: "{{ mongodb_net_port }}" - timeout: 120 - with_items: "{{ mongodb_net_bindip.split(',') | map('replace', '0.0.0.0', '127.0.0.1') | list }}" +- name: Flush all handlers at this point + meta: flush_handlers diff --git a/tasks/install.debian.yml b/tasks/install.debian.yml index 76c0a2f..4a8151e 100644 --- a/tasks/install.debian.yml +++ b/tasks/install.debian.yml @@ -47,8 +47,12 @@ or mongodb_repository[mongodb_major_version] is not defined)) - name: Add APT repository - apt_repository: repo="{{ mongodb_repository[item] }}" update_cache=yes + apt_repository: + repo: "{{ mongodb_repository[version_item] }}" + update_cache: yes with_items: "{{ mongodb_major_version }}" + loop_control: + loop_var: version_item when: mongodb_package == 'mongodb-org' - name: Install MongoDB and numactl packages @@ -66,10 +70,8 @@ - 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: ansible_service_mgr == "systemd" - -- block: - - meta: flush_handlers - when: ansible_service_mgr == "systemd" + notify: + - reload systemd - name: Install PyMongo package apt: diff --git a/tasks/main.yml b/tasks/main.yml index 79e7578..c8114b3 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -20,14 +20,14 @@ - name: Include replication and auth configuration include: replication_init_auth.yml - when: ( mongodb_replication_replset + when: ( mongodb_replication_replset is defined and mongodb_security_authorization == 'enabled' and mongodb_master is defined and mongodb_master ) tags: [mongodb] - name: Include replication configuration include: replication.yml - when: mongodb_replication_replset + when: mongodb_replication_replset is defined and mongodb_replication_replset tags: [mongodb] - name: Check where admin user already exists @@ -51,7 +51,7 @@ tags: [mongodb] - name: create normal users with replicaset - mongodb_user_fixed: + mongodb_user: database: "{{ item.database }}" name: "{{ item.name }}" password: "{{ item.password }}" @@ -70,7 +70,7 @@ tags: [mongodb] - name: create normal users without replicaset - mongodb_user_fixed: + mongodb_user: database: "{{ item.database }}" name: "{{ item.name }}" password: "{{ item.password }}" @@ -87,7 +87,7 @@ tags: [mongodb] - name: create oplog user with replicaset - mongodb_user_fixed: + mongodb_user: database: admin user: "{{ item.user }}" password: "{{ item.password }}" diff --git a/tasks/replication_init_auth.yml b/tasks/replication_init_auth.yml index e6f2f9d..76657db 100644 --- a/tasks/replication_init_auth.yml +++ b/tasks/replication_init_auth.yml @@ -19,7 +19,7 @@ ignore_errors: true - include: auth_initialization.yml - when: mongodb_replica_init|failed + when: mongodb_replica_init is failed - name: Replication configuration | 2nd Pt mongodb_replication: @@ -35,6 +35,6 @@ hidden: "{{ item.hidden|default(false) }}" priority: "{{ item.priority|default(1.0) }}" votes: "{{ item.votes|default(omit) }}" - when: mongodb_replica_init|failed + when: mongodb_replica_init is failed with_items: - "{{ mongodb_replication_params|default([]) }}" diff --git a/templates/mongod.conf.j2 b/templates/mongod.conf.j2 index 08aa7c9..bc45b45 100644 --- a/templates/mongod.conf.j2 +++ b/templates/mongod.conf.j2 @@ -2,7 +2,7 @@ net: bindIp: {{ mongodb_net_bindip }} - {% if mongodb_major_version is version_compare("3.6", "<") -%} + {% if mongodb_major_version is version("3.6", "<") -%} http: enabled: {{ mongodb_net_http_enabled | to_nice_json }} {% endif -%} @@ -39,9 +39,7 @@ security: storage: dbPath: {{ mongodb_storage_dbpath }} directoryPerDB: {{ mongodb_storage_dirperdb | to_nice_json }} - {% if mongodb_major_version is version_compare("3.0", ">=") -%} engine: {{ mongodb_storage_engine }} - {% endif -%} journal: enabled: {{ mongodb_storage_journal_enabled | to_nice_json }} {% if mongodb_storage_engine == 'mmapv1' -%} diff --git a/templates/mongod_init.conf.j2 b/templates/mongod_init.conf.j2 index 9ce98cd..b32538f 100644 --- a/templates/mongod_init.conf.j2 +++ b/templates/mongod_init.conf.j2 @@ -2,7 +2,7 @@ net: bindIp: '127.0.0.1' - {% if mongodb_major_version | version_compare("3.6", "<") -%} + {% if mongodb_major_version is version("3.6", "<") -%} http: enabled: {{ mongodb_net_http_enabled | to_nice_json }} {% endif -%} @@ -22,9 +22,7 @@ security: storage: dbPath: {{ mongodb_storage_dbpath }} directoryPerDB: {{ mongodb_storage_dirperdb | to_nice_json }} - {% if mongodb_major_version | version_compare("3.0", ">=") -%} engine: {{ mongodb_storage_engine }} - {% endif -%} journal: enabled: {{ mongodb_storage_journal_enabled | to_nice_json }} {% if mongodb_storage_engine == 'mmapv1' -%} diff --git a/tests/host_vars/mongo3.yml b/tests/host_vars/mongo3.yml index 6f6b6f6..a63fa4c 100644 --- a/tests/host_vars/mongo3.yml +++ b/tests/host_vars/mongo3.yml @@ -1,6 +1,6 @@ --- mongodb_net_port: 30000 -mongodb_storage_journal_enabled: "{{ mongodb_major_version | version_compare('4.0', '>=') }}" +mongodb_storage_journal_enabled: "{{ mongodb_major_version is version('4.0', '>=') }}" mongodb_storage_smallfiles: true mongodb_storage_prealloc: false mongodb_replication_params: