add new module mongodb_info (#67846)

* add new module mongodb_info

* fix doc and examples

* add GPL info

* use LooseVersion in the function doc string
This commit is contained in:
Andrew Klychkov 2020-02-29 17:37:53 +03:00 committed by GitHub
parent 35fd86c6bc
commit 0bb2d67562
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 591 additions and 0 deletions

View file

@ -0,0 +1,444 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# 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 = r'''
---
module: mongodb_info
short_description: Gather information about MongoDB instance.
description:
- Gather information about MongoDB instance.
author: Andrew Klychkov (@Andersson007)
version_added: '2.10'
options:
filter:
description:
- Limit the collected information by comma separated string or YAML list.
- Allowable values are C(general), C(databases), C(total_size), C(parameters), C(users), C(roles).
- By default, collects all subsets.
- You can use '!' before value (for example, C(!users)) to exclude it from the information.
- If you pass including and excluding values to the filter, for example, I(filter=!general,users),
the excluding values, C(!general) in this case, will be ignored.
required: no
type: list
elements: str
login_user:
description:
- The MongoDB user to login with.
- Required when I(login_password) is specified.
required: no
type: str
login_password:
description:
- The password used to authenticate with.
- Required when I(login_user) is specified.
required: no
type: str
login_database:
description:
- The database where login credentials are stored.
required: no
type: str
default: 'admin'
login_host:
description:
- The host running MongoDB instance to login to.
required: no
type: str
default: 'localhost'
login_port:
description:
- The MongoDB server port to login to.
required: no
type: int
default: 27017
ssl:
description:
- Whether to use an SSL connection when connecting to the database.
required: no
type: bool
default: no
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: no
type: str
default: 'CERT_REQUIRED'
choices: ['CERT_NONE', 'CERT_OPTIONAL', 'CERT_REQUIRED']
notes:
- Requires the pymongo Python package on the remote host, version 2.4.2+.
requirements: [ 'pymongo' ]
'''
EXAMPLES = r'''
- name: Gather all supported information
mongodb_info:
login_user: admin
login_password: secret
register: result
- name: Show gathered info
debug:
msg: '{{ result }}'
- name: Gather only information about databases and their total size
mongodb_info:
login_user: admin
login_password: secret
filter: databases, total_size
- name: Gather all information except parameters
mongodb_info:
login_user: admin
login_password: secret
filter: '!parameters'
'''
RETURN = r'''
general:
description: General instance information.
returned: always
type: dict
sample: {"allocator": "tcmalloc", "bits": 64, "storageEngines": ["biggie"], "version": "4.2.3", "maxBsonObjectSize": 16777216}
databases:
description: Database information.
returned: always
type: dict
sample: {"admin": {"empty": false, "sizeOnDisk": 245760}, "config": {"empty": false, "sizeOnDisk": 110592}}
total_size:
description: Total size of all databases in bytes.
returned: always
type: int
sample: 397312
users:
description: User information.
returned: always
type: dict
sample: {"new_user": {"_id": "config.new_user", "db": "config", "mechanisms": ["SCRAM-SHA-1", "SCRAM-SHA-256"], "roles": []}}
roles:
description: Role information.
returned: always
type: dict
sample: {"restore": {"db": "admin", "inheritedRoles": [], "isBuiltin": true, "roles": []}}
parameters:
description: Server parameters information.
returned: always
type: dict
sample: {"maxOplogTruncationPointsAfterStartup": 100, "maxOplogTruncationPointsDuringStartup": 100, "maxSessions": 1000000}
'''
import traceback
from uuid import UUID
import ssl as ssl_lib
from distutils.version import LooseVersion
PYMONGO_IMP_ERR = None
try:
from pymongo import version as PyMongoVersion
from pymongo import MongoClient
except ImportError:
PYMONGO_IMP_ERR = traceback.format_exc()
pymongo_found = False
else:
pymongo_found = True
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible.module_utils.six import iteritems
# =========================================
# MongoDB module specific support methods.
#
def check_compatibility(module, srv_version, driver_version):
"""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.
srv_version (LooseVersion): MongoDB server version.
driver_version (LooseVersion): Pymongo version.
"""
msg = 'pymongo driver version and MongoDB version are incompatible: '
if srv_version >= LooseVersion('4.2') and driver_version < LooseVersion('3.9'):
msg += 'you must use pymongo 3.9+ with MongoDB >= 4.2'
module.fail_json(msg=msg)
elif srv_version >= LooseVersion('4.0') and driver_version < LooseVersion('3.7'):
msg += 'you must use pymongo 3.7+ with MongoDB >= 4.0'
module.fail_json(msg=msg)
elif srv_version >= LooseVersion('3.6') and driver_version < LooseVersion('3.6'):
msg += 'you must use pymongo 3.6+ with MongoDB >= 3.6'
module.fail_json(msg=msg)
elif srv_version >= LooseVersion('3.4') and driver_version < LooseVersion('3.4'):
msg += 'you must use pymongo 3.4+ with MongoDB >= 3.4'
module.fail_json(msg=msg)
elif srv_version >= LooseVersion('3.2') and driver_version < LooseVersion('3.2'):
msg += 'you must use pymongo 3.2+ with MongoDB >= 3.2'
module.fail_json(msg=msg)
elif srv_version >= LooseVersion('3.0') and driver_version <= LooseVersion('2.8'):
msg += 'you must use pymongo 2.8+ with MongoDB 3.0'
module.fail_json(msg=msg)
elif srv_version >= LooseVersion('2.6') and driver_version <= LooseVersion('2.7'):
msg += 'you must use pymongo 2.7+ with MongoDB 2.6'
module.fail_json(msg=msg)
class MongoDbInfo():
"""Class for gathering MongoDB instance information.
Args:
module (AnsibleModule): Object of AnsibleModule class.
client (pymongo): pymongo client object to interact with the database.
"""
def __init__(self, module, client):
self.module = module
self.client = client
self.admin_db = self.client.admin
self.info = {
'general': {},
'databases': {},
'total_size': {},
'parameters': {},
'users': {},
'roles': {},
}
def get_info(self, filter_):
"""Get MongoDB instance information and return it based on filter_.
Args:
filter_ (list): List of collected subsets (e.g., general, users, etc.),
when it is empty, return all available information.
"""
self.__collect()
inc_list = []
exc_list = []
if filter_:
partial_info = {}
for fi in filter_:
if fi.lstrip('!') not in self.info:
self.module.warn("filter element '%s' is not allowable, ignored" % fi)
continue
if fi[0] == '!':
exc_list.append(fi.lstrip('!'))
else:
inc_list.append(fi)
if inc_list:
for i in self.info:
if i in inc_list:
partial_info[i] = self.info[i]
else:
for i in self.info:
if i not in exc_list:
partial_info[i] = self.info[i]
return partial_info
else:
return self.info
def __collect(self):
"""Collect information."""
# Get general info:
self.info['general'] = self.client.server_info()
# Get parameters:
self.info['parameters'] = self.get_parameters_info()
# Gather info about databases and their total size:
self.info['databases'], self.info['total_size'] = self.get_db_info()
for dbname, val in iteritems(self.info['databases']):
# Gather info about users for each database:
self.info['users'].update(self.get_users_info(dbname))
# Gather info about roles for each database:
self.info['roles'].update(self.get_roles_info(dbname))
def get_roles_info(self, dbname):
"""Gather information about roles.
Args:
dbname (str): Database name to get role info from.
Returns a dictionary with role information.
"""
db = self.client[dbname]
result = db.command({'rolesInfo': 1, 'showBuiltinRoles': True})['roles']
roles_dict = {}
for elem in result:
roles_dict[elem['role']] = {}
for key, val in iteritems(elem):
if key == 'role':
continue
roles_dict[elem['role']][key] = val
return roles_dict
def get_users_info(self, dbname):
"""Gather information about users.
Args:
dbname (str): Database name to get user info from.
Returns a dictionary with user information.
"""
db = self.client[dbname]
result = db.command({'usersInfo': 1})['users']
users_dict = {}
for elem in result:
users_dict[elem['user']] = {}
for key, val in iteritems(elem):
if key == 'user':
continue
if isinstance(val, UUID):
val = val.hex
users_dict[elem['user']][key] = val
return users_dict
def get_db_info(self):
"""Gather information about databases.
Returns a dictionary with database information.
"""
result = self.admin_db.command({'listDatabases': 1})
total_size = int(result['totalSize'])
result = result['databases']
db_dict = {}
for elem in result:
db_dict[elem['name']] = {}
for key, val in iteritems(elem):
if key == 'name':
continue
if key == 'sizeOnDisk':
val = int(val)
db_dict[elem['name']][key] = val
return db_dict, total_size
def get_parameters_info(self):
"""Gather parameters information.
Returns a dictionary with parameters.
"""
return self.admin_db.command({'getParameter': '*'})
# ================
# Module execution
#
def main():
argument_spec = dict(
login_user=dict(type='str', required=False),
login_password=dict(type='str', required=False, no_log=True),
login_database=dict(type='str', required=False, default='admin'),
login_host=dict(type='str', required=False, default='localhost'),
login_port=dict(type='int', required=False, default=27017),
ssl=dict(type='bool', required=False, default=False),
ssl_cert_reqs=dict(type='str', required=False, default='CERT_REQUIRED',
choices=['CERT_NONE', 'CERT_OPTIONAL', 'CERT_REQUIRED']),
filter=dict(type='list', elements='str', required=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_together=[['login_user', 'login_password']],
)
if not pymongo_found:
module.fail_json(msg=missing_required_lib('pymongo'), exception=PYMONGO_IMP_ERR)
login_user = module.params['login_user']
login_password = module.params['login_password']
login_database = module.params['login_database']
login_host = module.params['login_host']
login_port = module.params['login_port']
ssl = module.params['ssl']
filter_ = module.params['filter']
if filter_:
filter_ = [f.strip() for f in filter_]
connection_params = {
'host': login_host,
'port': login_port,
}
if ssl:
connection_params['ssl'] = ssl
connection_params['ssl_cert_reqs'] = getattr(ssl_lib, module.params['ssl_cert_reqs'])
client = MongoClient(**connection_params)
if login_user:
try:
client.admin.authenticate(login_user, login_password, source=login_database)
except Exception as e:
module.fail_json(msg='Unable to authenticate: %s' % to_native(e))
# Get server version:
try:
srv_version = LooseVersion(client.server_info()['version'])
except Exception as e:
module.fail_json(msg='Unable to get MongoDB server version: %s' % to_native(e))
# Get driver version::
driver_version = LooseVersion(PyMongoVersion)
# Check driver and server version compatibility:
check_compatibility(module, srv_version, driver_version)
# Initialize an object and start main work:
mongodb = MongoDbInfo(module, client)
module.exit_json(changed=False, **mongodb.get_info(filter_))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,8 @@
destructive
shippable/posix/group1
skip/aix
skip/osx
skip/freebsd
skip/ubuntu
skip/redhat
skip/opensuse

View file

@ -0,0 +1,4 @@
mongodb_default_port: 27017
mongodb_admin_user: admin
mongodb_admin_password: admin
mongodb_default_db: admin

View file

@ -0,0 +1,3 @@
dependencies:
- setup_mongodb_v4
- setup_remote_tmp_dir

View file

@ -0,0 +1,8 @@
# Copyright 2020, Andrew Klychkov <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Main mongodb_info module tests
- import_tasks: mongodb_info.yml
when:
- ansible_distribution == 'CentOS'
- ansible_distribution_major_version is version('7', '>=')

View file

@ -0,0 +1,60 @@
# Copyright 2020, Andrew Klychkov <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- vars:
task_parameters: &task_parameters
register: result
mongo_parameters: &mongo_parameters
login_port: '{{ mongodb_default_port }}'
login_user: '{{ mongodb_admin_user }}'
login_password: '{{ mongodb_admin_password }}'
login_database: '{{ mongodb_default_db }}'
block:
- name: Get info
<<: *task_parameters
mongodb_info:
<<: *mongo_parameters
- assert:
that:
- result is not changed
- result.general.version == '4.2.3'
- result.databases.admin
- result.total_size
- result.users.admin
- result.roles.backup
- result.parameters.logLevel == 0
- name: Get info with filter
<<: *task_parameters
mongodb_info:
<<: *mongo_parameters
filter: general, total_size
- assert:
that:
- result is not changed
- result.general.version == '4.2.3'
- result.total_size
- result.databases is not defined
- result.parameters is not defined
- result.users is not defined
- result.roles is not defined
- name: Get info with filter
<<: *task_parameters
mongodb_info:
<<: *mongo_parameters
filter: '!parameters'
- assert:
that:
- result is not changed
- result.general.version == '4.2.3'
- result.databases.admin
- result.total_size
- result.users.admin
- result.roles.backup
- result.parameters is not defined

View file

@ -0,0 +1,13 @@
mongodb_repo_name: mongodb
mongodb_repo_descr: MongoDB Repository
mongodb_repo_link: http://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.2/$basearch/
mongodb_gpgkey: https://www.mongodb.org/static/pgp/server-4.2.asc
mongodb_pkgs:
- mongodb-org
pymongo_repo: git://github.com/mongodb/mongo-python-driver.git
mongodb_default_port: 27017
mongodb_admin_user: admin
mongodb_admin_password: admin
mongodb_default_db: admin

View file

@ -0,0 +1,10 @@
- name: Remove MongoDB packages
yum:
name: '{{ mongodb_pkgs }}'
state: absent
- name: Stop MongoDB
systemd:
name: mongod.service
state: stopped
notify: Stop MongoDB

View file

@ -0,0 +1,7 @@
# Copyright 2020, Andrew Klychkov <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- import_tasks: setup_mongodb.yml
when:
- ansible_distribution == 'CentOS'
- ansible_distribution_major_version is version('7', '>=')

View file

@ -0,0 +1,34 @@
# Copyright 2020, Andrew Klychkov <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- name: Set up repo
yum_repository:
name: '{{ mongodb_repo_name }}'
description: '{{ mongodb_repo_descr }}'
baseurl: '{{ mongodb_repo_link }}'
gpgkey: '{{ mongodb_gpgkey }}'
gpgcheck: yes
- name: Install MongoDB packages
yum:
name: '{{ mongodb_pkgs }}'
state: present
notify: Remove MongoDB packages
- name: Install pymongo
shell: git clone '{{ pymongo_repo }}' pymongo && cd pymongo/ && python setup.py install
- name: Start MongoDB
systemd:
name: mongod.service
state: started
notify: Stop MongoDB
- name: Create admin user
mongodb_user:
login_port: '{{ mongodb_default_port }}'
database: '{{ mongodb_default_db }}'
name: '{{ mongodb_admin_user }}'
password: '{{ mongodb_admin_password }}'
roles: root
state: present