268 lines
10 KiB
Python
268 lines
10 KiB
Python
# (c) 2016, Marcos Diez <marcos@unitron.com.br>
|
|
# https://github.com/marcosdiez/
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = '''
|
|
name: mongodb
|
|
version_added: "1.0.0"
|
|
short_description: lookup info from MongoDB
|
|
description:
|
|
- 'The ``MongoDB`` lookup runs the *find()* command on a given *collection* on a given *MongoDB* server.'
|
|
- 'The result is a list of jsons, so slightly different from what PyMongo returns. In particular, *timestamps* are converted to epoch integers.'
|
|
options:
|
|
connect_string:
|
|
description:
|
|
- Can be any valid MongoDB connection string, supporting authentication, replica sets, etc.
|
|
- "More info at U(https://docs.mongodb.org/manual/reference/connection-string/)"
|
|
default: "mongodb://localhost/"
|
|
database:
|
|
description:
|
|
- Name of the database which the query will be made
|
|
required: True
|
|
collection:
|
|
description:
|
|
- Name of the collection which the query will be made
|
|
required: True
|
|
filter:
|
|
description:
|
|
- Criteria of the output
|
|
type: 'dict'
|
|
default: {}
|
|
projection:
|
|
description:
|
|
- Fields you want returned
|
|
type: dict
|
|
default: {}
|
|
skip:
|
|
description:
|
|
- How many results should be skipped
|
|
type: integer
|
|
limit:
|
|
description:
|
|
- How many results should be shown
|
|
type: integer
|
|
sort:
|
|
description:
|
|
- Sorting rules.
|
|
- Please use the strings C(ASCENDING) and C(DESCENDING) to set the order.
|
|
- Check the example for more information.
|
|
type: list
|
|
elements: list
|
|
default: []
|
|
extra_connection_parameters:
|
|
description:
|
|
- Extra connection parameters that to be sent to pymongo.MongoClient
|
|
- Check the example to see how to connect to mongo using an SSL certificate.
|
|
- "All possible parameters are here: U(https://api.mongodb.com/python/current/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient)"
|
|
type: dict
|
|
default: {}
|
|
notes:
|
|
- "Please check https://api.mongodb.org/python/current/api/pymongo/collection.html?highlight=find#pymongo.collection.Collection.find for more details."
|
|
requirements:
|
|
- pymongo >= 2.4 (python library)
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- hosts: localhost
|
|
gather_facts: false
|
|
vars:
|
|
mongodb_parameters:
|
|
#mandatory parameters
|
|
database: 'local'
|
|
collection: "startup_log"
|
|
#optional
|
|
connection_string: "mongodb://localhost/"
|
|
# connection_string: "mongodb://username:password@my.server.com:27017/"
|
|
# extra_connection_parameters: { "ssl" : True , "ssl_certfile": /etc/self_signed_certificate.pem" }
|
|
#optional query parameters, we accept any parameter from the normal mongodb query.
|
|
# filter: { "hostname": "u18" }
|
|
projection: { "pid": True , "_id" : False , "hostname" : True }
|
|
skip: 0
|
|
limit: 1
|
|
sort: [ [ "startTime" , "ASCENDING" ] , [ "age", "DESCENDING" ] ]
|
|
tasks:
|
|
- debug: msg="The PID from MongoDB is {{ lookup('mongodb', mongodb_parameters ).pid }}"
|
|
|
|
- debug: msg="The HostName from the MongoDB server is {{ lookup('mongodb', mongodb_parameters ).hostname }}"
|
|
|
|
- debug: msg="Mongo DB is stored at {{ lookup('mongodb', mongodb_parameters_inline )}}"
|
|
vars:
|
|
mongodb_parameters_inline:
|
|
database: 'local'
|
|
collection: "startup_log"
|
|
connection_string: "mongodb://localhost/"
|
|
limit: 1
|
|
projection: { "cmdline.storage": True }
|
|
|
|
# lookup syntax, does the same as below
|
|
- debug: msg="The hostname is {{ item.hostname }} and the pid is {{ item.pid }}"
|
|
loop: "{{ lookup('mongodb', mongodb_parameters, wantlist=True) }}"
|
|
|
|
# query syntax, does the same as above
|
|
- debug: msg="The hostname is {{ item.hostname }} and the pid is {{ item.pid }}"
|
|
loop: "{{ query('mongodb', mongodb_parameters) }}"
|
|
|
|
- name: "Raw output from the mongodb lookup (a json with pid and hostname )"
|
|
debug: msg="{{ lookup('mongodb', mongodb_parameters) }}"
|
|
|
|
- name: "Yet another mongodb query, now with the parameters on the task itself"
|
|
debug: msg="pid={{item.pid}} hostname={{item.hostname}} version={{ item.buildinfo.version }}"
|
|
with_mongodb:
|
|
- database: 'local'
|
|
collection: "startup_log"
|
|
connection_string: "mongodb://localhost/"
|
|
limit: 1
|
|
projection: { "pid": True , "hostname": True , "buildinfo.version": True }
|
|
|
|
# Please notice this specific query may result more than one result. This is expected
|
|
- name: "Shows the whole output from mongodb"
|
|
debug: msg="{{ item }}"
|
|
with_mongodb:
|
|
- database: 'local'
|
|
collection: "startup_log"
|
|
connection_string: "mongodb://localhost/"
|
|
|
|
|
|
'''
|
|
|
|
RETURN = """
|
|
_list_of_jsons:
|
|
description:
|
|
- a list of JSONs with the results of the MongoDB query.
|
|
type: list
|
|
"""
|
|
|
|
import datetime
|
|
|
|
from ansible.module_utils.six import string_types, integer_types
|
|
from ansible.module_utils._text import to_native
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
try:
|
|
from pymongo import ASCENDING, DESCENDING
|
|
from pymongo.errors import ConnectionFailure
|
|
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
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
|
|
def _fix_sort_parameter(self, sort_parameter):
|
|
if sort_parameter is None:
|
|
return sort_parameter
|
|
|
|
if not isinstance(sort_parameter, list):
|
|
raise AnsibleError(u"Error. Sort parameters must be a list, not [ {0} ]".format(sort_parameter))
|
|
|
|
for item in sort_parameter:
|
|
self._convert_sort_string_to_constant(item)
|
|
|
|
return sort_parameter
|
|
|
|
def _convert_sort_string_to_constant(self, item):
|
|
original_sort_order = item[1]
|
|
sort_order = original_sort_order.upper()
|
|
if sort_order == u"ASCENDING":
|
|
item[1] = ASCENDING
|
|
elif sort_order == u"DESCENDING":
|
|
item[1] = DESCENDING
|
|
# else the user knows what s/he is doing and we won't predict. PyMongo will return an error if necessary
|
|
|
|
def convert_mongo_result_to_valid_json(self, result):
|
|
if result is None:
|
|
return result
|
|
if isinstance(result, integer_types + (float, bool)):
|
|
return result
|
|
if isinstance(result, string_types):
|
|
return result
|
|
elif isinstance(result, list):
|
|
new_list = []
|
|
for elem in result:
|
|
new_list.append(self.convert_mongo_result_to_valid_json(elem))
|
|
return new_list
|
|
elif isinstance(result, dict):
|
|
new_dict = {}
|
|
for key in result.keys():
|
|
value = result[key] # python2 and 3 compatible....
|
|
new_dict[key] = self.convert_mongo_result_to_valid_json(value)
|
|
return new_dict
|
|
elif isinstance(result, datetime.datetime):
|
|
# epoch
|
|
return (result - datetime.datetime(1970, 1, 1)). total_seconds()
|
|
else:
|
|
# failsafe
|
|
return u"{0}".format(result)
|
|
|
|
def run(self, terms, variables, **kwargs):
|
|
try:
|
|
return self._run_helper(terms)
|
|
except Exception as e:
|
|
print(u"There was an exception on the mongodb_lookup: {0}".format(to_native(e)))
|
|
raise e
|
|
|
|
def _run_helper(self, terms):
|
|
if not pymongo_found:
|
|
raise AnsibleError(u"pymongo is required in the control node (this machine) for mongodb lookup.")
|
|
ret = []
|
|
for term in terms:
|
|
for required_parameter in [u"database", u"collection"]:
|
|
if required_parameter not in term:
|
|
raise AnsibleError(u"missing mandatory parameter [{0}]".format(required_parameter))
|
|
|
|
connection_string = term.get(u'connection_string', u"mongodb://localhost")
|
|
database = term[u"database"]
|
|
collection = term[u'collection']
|
|
extra_connection_parameters = term.get(u'extra_connection_parameters', {})
|
|
|
|
if u"extra_connection_parameters" in term:
|
|
del term[u"extra_connection_parameters"]
|
|
if u"connection_string" in term:
|
|
del term[u"connection_string"]
|
|
del term[u"database"]
|
|
del term[u"collection"]
|
|
|
|
if u"sort" in term:
|
|
term[u"sort"] = self._fix_sort_parameter(term[u"sort"])
|
|
|
|
# all other parameters are sent to mongo, so we are future and past proof
|
|
|
|
try:
|
|
client = MongoClient(connection_string, **extra_connection_parameters)
|
|
results = client[database][collection].find(**term)
|
|
|
|
for result in results:
|
|
result = self.convert_mongo_result_to_valid_json(result)
|
|
ret.append(result)
|
|
|
|
except ConnectionFailure as e:
|
|
raise AnsibleError(u'unable to connect to database: %s' % str(e))
|
|
|
|
return ret
|