diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:52:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:52:35 +0000 |
commit | 7fec0b69a082aaeec72fee0612766aa42f6b1b4d (patch) | |
tree | efb569b86ca4da888717f5433e757145fa322e08 /ansible_collections/community/mongodb/plugins | |
parent | Releasing progress-linux version 7.7.0+dfsg-3~progress7.99u1. (diff) | |
download | ansible-7fec0b69a082aaeec72fee0612766aa42f6b1b4d.tar.xz ansible-7fec0b69a082aaeec72fee0612766aa42f6b1b4d.zip |
Merging upstream version 9.4.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/mongodb/plugins')
13 files changed, 998 insertions, 206 deletions
diff --git a/ansible_collections/community/mongodb/plugins/cache/mongodb.py b/ansible_collections/community/mongodb/plugins/cache/mongodb.py index b51b7b293..0fa7a2cd8 100644 --- a/ansible_collections/community/mongodb/plugins/cache/mongodb.py +++ b/ansible_collections/community/mongodb/plugins/cache/mongodb.py @@ -182,7 +182,7 @@ class CacheModule(BaseCacheModule): def contains(self, key): with self._collection() as collection: - return bool(collection.count({'_id': self._make_key(key)})) + return bool(collection.count_documents({'_id': self._make_key(key)})) def delete(self, key): del self._cache[key] diff --git a/ansible_collections/community/mongodb/plugins/doc_fragments/atlas_options.py b/ansible_collections/community/mongodb/plugins/doc_fragments/atlas_options.py new file mode 100644 index 000000000..dfdee3325 --- /dev/null +++ b/ansible_collections/community/mongodb/plugins/doc_fragments/atlas_options.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021 T-Systems MMS +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This module 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. +# +# This software 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 this software. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + # Documentation for global options that are always the same + DOCUMENTATION = r''' +options: + api_username: + description: + - The username for use in authentication with the Atlas API. + - Can use API users and tokens (public key is username) + type: str + required: True + aliases: [apiUsername] + api_password: + description: + - The password for use in authentication with the Atlas API. + - Can use API users and tokens (private key is password) + type: str + required: True + aliases: [apiPassword] + group_id: + description: + - Unique identifier for the Atlas project. + type: str + required: True + aliases: [groupId] + state: + description: + - State of the ressource. + choices: [ "present", "absent" ] + default: present + type: str +''' diff --git a/ansible_collections/community/mongodb/plugins/module_utils/mongodb_atlas.py b/ansible_collections/community/mongodb/plugins/module_utils/mongodb_atlas.py new file mode 100644 index 000000000..a32f3e1de --- /dev/null +++ b/ansible_collections/community/mongodb/plugins/module_utils/mongodb_atlas.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json +from collections import defaultdict + +from ansible.module_utils.urls import fetch_url + +try: + from urllib import quote +except ImportError: + # noinspection PyCompatibility, PyUnresolvedReferences + from urllib.parse import ( + quote, + ) # pylint: disable=locally-disabled, import-error, no-name-in-module + + +class AtlasAPIObject: + module = None + + def __init__( + self, module, object_name, group_id, path, data, data_is_array=False + ): + self.module = module + self.path = path + self.data = data + self.group_id = group_id + self.object_name = object_name + self.data_is_array = data_is_array + + self.module.params["url_username"] = self.module.params["api_username"] + self.module.params["url_password"] = self.module.params["api_password"] + + def call_url(self, path, data="", method="GET"): + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + if self.data_is_array and data != "": + data = "[" + data + "]" + + url = ( + "https://cloud.mongodb.com/api/atlas/v1.0/groups/" + + self.group_id + + path + ) + rsp, info = fetch_url( + module=self.module, + url=url, + data=data, + headers=headers, + method=method, + ) + + content = "" + error = "" + if rsp and info["status"] not in (204, 404): + content = json.loads(rsp.read()) + if info["status"] >= 400: + try: + content = json.loads(info["body"]) + error = content["reason"] + if "detail" in content: + error += ". Detail: " + content["detail"] + except ValueError: + error = info["msg"] + if info["status"] < 0: + error = info["msg"] + return {"code": info["status"], "data": content, "error": error} + + def exists(self): + additional_path = "" + if self.path == "/databaseUsers": + additional_path = "/admin" + ret = self.call_url( + path=self.path + + additional_path + + "/" + + quote(self.data[self.object_name], "") + ) + if ret["code"] == 200: + return True + return False + + def create(self): + ret = self.call_url( + path=self.path, + data=self.module.jsonify(self.data), + method="POST", + ) + return ret + + def delete(self): + additional_path = "" + if self.path == "/databaseUsers": + additional_path = "/admin" + ret = self.call_url( + path=self.path + + additional_path + + "/" + + quote(self.data[self.object_name], ""), + method="DELETE", + ) + return ret + + def modify(self): + additional_path = "" + if self.path == "/databaseUsers": + additional_path = "/admin" + ret = self.call_url( + path=self.path + + additional_path + + "/" + + quote(self.data[self.object_name], ""), + data=self.module.jsonify(self.data), + method="PATCH", + ) + return ret + + def diff(self): + additional_path = "" + if self.path == "/databaseUsers": + additional_path = "/admin" + ret = self.call_url( + path=self.path + + additional_path + + "/" + + quote(self.data[self.object_name], ""), + method="GET", + ) + + data_from_atlas = json.loads(self.module.jsonify(ret["data"])) + data_from_task = json.loads(self.module.jsonify(self.data)) + + diff = defaultdict(dict) + for key, value in data_from_atlas.items(): + if key in data_from_task.keys() and value != data_from_task[key]: + diff["before"][key] = "{val}".format(val=value) + diff["after"][key] = "{val}".format(val=data_from_task[key]) + return diff + + def update(self, state): + changed = False + diff_result = {"before": "", "after": ""} + if self.exists(): + diff_result.update({"before": "state: present\n"}) + if state == "absent": + if self.module.check_mode: + diff_result.update({"after": "state: absent\n"}) + self.module.exit_json( + changed=True, + object_name=self.data[self.object_name], + diff=diff_result, + ) + else: + try: + ret = self.delete() + if ret["code"] == 204 or ret["code"] == 202: + changed = True + diff_result.update({"after": "state: absent\n"}) + else: + self.module.fail_json( + msg="bad return code while deleting: %d. Error message: %s" + % (ret["code"], ret["error"]) + ) + except Exception as e: + self.module.fail_json( + msg="exception when deleting: " + str(e) + ) + + else: + diff_result.update(self.diff()) + if self.module.check_mode: + if diff_result["after"] != "": + changed = True + self.module.exit_json( + changed=changed, + object_name=self.data[self.object_name], + data=self.data, + diff=diff_result, + ) + if diff_result["after"] != "": + if self.path == "/whitelist": + ret = self.create() + else: + ret = self.modify() + if ret["code"] == 200 or ret["code"] == 201: + changed = True + else: + self.module.fail_json( + msg="bad return code while modifying: %d. Error message: %s" + % (ret["code"], ret["error"]) + ) + + else: + diff_result.update({"before": "state: absent\n"}) + if state == "present": + if self.module.check_mode: + changed = True + diff_result.update({"after": "state: created\n"}) + else: + try: + ret = self.create() + if ret["code"] == 201: + changed = True + diff_result.update({"after": "state: created\n"}) + else: + self.module.fail_json( + msg="bad return code while creating: %d. Error message: %s" + % (ret["code"], ret["error"]) + ) + except Exception as e: + self.module.fail_json( + msg="exception while creating: " + str(e) + ) + return changed, diff_result diff --git a/ansible_collections/community/mongodb/plugins/module_utils/mongodb_common.py b/ansible_collections/community/mongodb/plugins/module_utils/mongodb_common.py index e1ab27293..2a748b96f 100644 --- a/ansible_collections/community/mongodb/plugins/module_utils/mongodb_common.py +++ b/ansible_collections/community/mongodb/plugins/module_utils/mongodb_common.py @@ -168,9 +168,9 @@ def rename_ssl_option_for_pymongo4(connection_options): when the driver use is >= PyMongo 4 """ if int(PyMongoVersion[0]) >= 4: - if connection_options.get('ssl_cert_reqs', None) == 'CERT_NONE': - connection_options['tlsAllowInvalidCertificates'] = False - elif connection_options.get('ssl_cert_reqs', None) == 'CERT_REQUIRED': + if connection_options.get('ssl_cert_reqs', None) in ('CERT_NONE', ssl_lib.CERT_NONE): + connection_options['tlsAllowInvalidCertificates'] = True + elif connection_options.get('ssl_cert_reqs', None) in ('CERT_REQUIRED', ssl_lib.CERT_REQUIRED): connection_options['tlsAllowInvalidCertificates'] = False connection_options.pop('ssl_cert_reqs', None) if connection_options.get('ssl_ca_certs', None) is not None: @@ -395,6 +395,7 @@ def member_dicts_different(conf, member_config): "hidden": False, "priority": {"nonarbiter": 1.0, "arbiter": 0}, "tags": {}, + "horizons": {}, "secondardDelaySecs": 0, "votes": 1 } diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_cluster.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_cluster.py new file mode 100644 index 000000000..e8aa6e43f --- /dev/null +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_cluster.py @@ -0,0 +1,238 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 T-Systems MMS +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This module 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. +# +# This software 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 this software. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: mongodb_atlas_cluster +short_description: Manage database clusters in Atlas +description: + - The clusters module provides access to your cluster configurations. + - The module lets you create, edit and delete clusters. + - L(API Documentation,https://docs.atlas.mongodb.com/reference/api/clusters/) +author: "Martin Schurz (@schurzi)" +extends_documentation_fragment: community.mongodb.atlas_options +options: + name: + description: + - Name of the cluster as it appears in Atlas. Once the cluster is created, its name cannot be changed. + type: str + required: True + mongo_db_major_version: + description: + - Version of the cluster to deploy. + - Atlas always deploys the cluster with the latest stable release of the specified version. + - You can upgrade to a newer version of MongoDB when you modify a cluster. + choices: [ "4.2", "4.4", "5.0", "6.0", "7.0" ] + type: str + aliases: [ "mongoDBMajorVersion" ] + cluster_type: + description: + - Type of the cluster that you want to create. + choices: [ "REPLICASET", "SHARDED" ] + default: "REPLICASET" + type: str + aliases: [ "clusterType" ] + replication_factor: + description: + - Number of replica set members. Each member keeps a copy of your databases, providing high availability and data redundancy. + choices: [ 3, 5, 7 ] + default: 3 + type: int + aliases: [ "replicationFactor" ] + auto_scaling: + description: + - Configure your cluster to automatically scale its storage and cluster tier. + suboptions: + disk_gb_enabled: + type: bool + description: + - Specifies whether disk auto-scaling is enabled. The default is true. + aliases: [ "diskGBEnabled" ] + required: False + type: dict + aliases: [ "autoScaling" ] + provider_settings: + description: + - Configuration for the provisioned servers on which MongoDB runs. + - The available options are specific to the cloud service provider. + suboptions: + provider_name: + required: True + type: str + description: + - Cloud service provider on which the servers are provisioned. + aliases: [ "providerName" ] + region_name: + required: True + type: str + description: + - Physical location of your MongoDB cluster. + aliases: [ "regionName" ] + instance_size_name: + required: True + type: str + description: + - Atlas provides different cluster tiers, each with a default storage capacity and RAM size. + - The cluster you select is used for all the data-bearing servers in your cluster tier. + aliases: [ "instanceSizeName" ] + required: True + type: dict + aliases: [ "providerSettings" ] + disk_size_gb: + description: + - Capacity, in gigabytes, of the host's root volume. Increase this number to add capacity, + up to a maximum possible value of 4096 (i.e., 4 TB). This value must be a positive integer. + type: int + aliases: [ "diskSizeGB" ] + provider_backup_enabled: + description: + - Flag that indicates if the cluster uses Cloud Backups for backups. + type: bool + aliases: [ "providerBackupEnabled" ] + pit_enabled: + description: + - Flag that indicates the cluster uses continuous cloud backups. + type: bool + aliases: [ "pitEnabled" ] +''' + +EXAMPLES = ''' + - name: test cluster + community.mongodb.mongodb_atlas_cluster: + api_username: "API_user" + api_password: "API_passwort_or_token" + group_id: "GROUP_ID" + name: "testcluster" + mongo_db_major_version: "4.0" + cluster_type: "REPLICASET" + provider_settings: + provider_name: "GCP" + region_name: "EUROPE_WEST_3" + instance_size_name: "M10" +... +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_atlas import ( + AtlasAPIObject, +) + + +# =========================================== +# Module execution. +# +def main(): + # add our own arguments + argument_spec = dict( + state=dict(default="present", choices=["absent", "present"]), + api_username=dict(required=True, aliases=['apiUsername']), + api_password=dict(required=True, no_log=True, aliases=['apiPassword']), + group_id=dict(required=True, aliases=['groupId']), + name=dict(required=True), + mongo_db_major_version=dict( + choices=["4.2", "4.4", "5.0", "6.0", "7.0"], + aliases=["mongoDBMajorVersion"] + ), + cluster_type=dict( + default="REPLICASET", choices=["REPLICASET", "SHARDED"], + aliases=["clusterType"] + ), + replication_factor=dict(default=3, type="int", choices=[3, 5, 7], aliases=["replicationFactor"]), + auto_scaling=dict( + type="dict", + options=dict( + disk_gb_enabled=dict(type="bool", aliases=["diskGBEnabled"]), + ), + aliases=["autoScaling"] + ), + provider_settings=dict( + type="dict", + required=True, + options=dict( + provider_name=dict(required=True, aliases=["providerName"]), + region_name=dict(required=True, aliases=["regionName"]), + instance_size_name=dict(required=True, aliases=["instanceSizeName"]), + ), + aliases=["providerSettings"] + ), + disk_size_gb=dict(type="int", aliases=["diskSizeGB"]), + provider_backup_enabled=dict(type="bool", aliases=["providerBackupEnabled"]), + pit_enabled=dict(type="bool", aliases=["pitEnabled"]), + ) + + # Define the main module + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + + data = { + "name": module.params["name"], + "clusterType": module.params["cluster_type"], + "replicationFactor": module.params["replication_factor"], + "providerSettings": { + "providerName": module.params["provider_settings"]["provider_name"], + "regionName": module.params["provider_settings"]["region_name"], + "instanceSizeName": module.params["provider_settings"]["instance_size_name"], + } + } + + # handle optional options + optional_vars = { + "mongo_db_major_version": "mongoDBMajorVersion", + "auto_scaling": "autoScaling", + "disk_size_gb": "diskSizeGB", + "provider_backup_enabled": "providerBackupEnabled", + "pit_enabled": "pitEnabled", + } + + for key in optional_vars: + if module.params[key] is not None: + if key == "auto_scaling": + data.update({optional_vars[key]: {"diskGBEnabled": module.params[key]["disk_gb_enabled"]}}) + else: + data.update({optional_vars[key]: module.params[key]}) + + try: + atlas = AtlasAPIObject( + module=module, + path="/clusters", + object_name="name", + group_id=module.params["group_id"], + data=data, + ) + except Exception as e: + module.fail_json( + msg="unable to connect to Atlas API. Exception message: %s" % e + ) + + changed, diff = atlas.update(module.params["state"]) + module.exit_json( + changed=changed, + data=atlas.data, + diff=diff, + ) + + +# import module snippets +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_ldap_user.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_ldap_user.py new file mode 100644 index 000000000..e816a264c --- /dev/null +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_ldap_user.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 T-Systems MMS +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This module 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. +# +# This software 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 this software. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: mongodb_atlas_ldap_user +short_description: Manage LDAP users in Atlas +description: + - The mongodb_atlas_ldap_user module lets you create LDAP groups on the admin database by mapping LDAP groups to MongoDB roles on your Atlas databases. + - Each user or group has a set of roles that provide access to the project's databases. + - L(API Documentation,https://docs.atlas.mongodb.com/security-ldaps/) +author: "Martin Schurz (@schurzi) / Derek Giri" +extends_documentation_fragment: community.mongodb.atlas_options +options: + database_name: + description: + - Database against which Atlas authenticates the user. + choices: ["admin", "$external"] + default: "admin" + type: str + aliases: [ "databaseName" ] + ldap_auth_type: + description: + - Type of LDAP authorization for the user i.e. USER or GROUP + choices: ["GROUP", "USER"] + default: "GROUP" + type: str + aliases: [ "ldapAuthType" ] + username: + description: + - Username for authenticating to MongoDB. + required: true + type: str + roles: + description: + - Array of this user's roles and the databases / collections on which the roles apply. + - A role must include folliwing elements + suboptions: + database_name: + required: true + type: str + description: + - Database on which the user has the specified role. + - A role on the admin database can include privileges that apply to the other databases. + aliases: [ "databaseName" ] + role_name: + required: true + type: str + description: + - Name of the role. This value can either be a built-in role or a custom role. + aliases: ["roleName" ] + required: true + type: list + elements: dict +''' + +EXAMPLES = ''' + - name: LDAP Group or Username + community.mongodb.mongodb_atlas_ldap_user: + api_username: "API_user" + api_password: "API_passwort_or_token" + atlas_ldap_user: "USER DN or GROUP DN" + group_id: "GROUP_ID" + database_name: "admin" + username: my_app_user + roles: + - database_name: private_info + role_name: read + - database_name: public_info + role_name: readWrite +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_atlas import ( + AtlasAPIObject, +) + + +# =========================================== +# Module execution. +# +def main(): + # add our own arguments + argument_spec = dict( + state=dict(default="present", choices=["absent", "present"]), + api_username=dict(required=True, aliases=['apiUsername']), + api_password=dict(required=True, no_log=True, aliases=['apiPassword']), + group_id=dict(required=True, aliases=['groupId']), + ldap_auth_type=dict(default="GROUP", choices=["GROUP", "USER"], aliases=["ldapAuthType"]), + database_name=dict(default="admin", choices=["admin", "$external"], aliases=["databaseName"]), + username=dict(required=True), + roles=dict( + required=True, + type="list", + elements="dict", + options=dict( + database_name=dict(required=True, aliases=["databaseName"]), + role_name=dict(required=True, aliases=["roleName"]), + ), + ), + ) + + # Define the main module + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + + data = { + "databaseName": module.params["database_name"], + "ldapAuthType": module.params["ldap_auth_type"], + "username": module.params["username"], + "roles": [], + } + + # remap keys to API format + for role in module.params.get("roles"): + data["roles"].append({ + "databaseName": role.get("database_name"), + "roleName": role.get("role_name") + }) + + try: + atlas = AtlasAPIObject( + module=module, + path="/databaseUsers", + object_name="username", + group_id=module.params["group_id"], + data=data, + ) + except Exception as e: + module.fail_json( + msg="unable to connect to Atlas API. Exception message: %s" % e + ) + + changed, diff = atlas.update(module.params["state"]) + module.exit_json( + changed=changed, + data=atlas.data, + diff=diff, + ) + + +# import module snippets +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_user.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_user.py new file mode 100644 index 000000000..ac8427b03 --- /dev/null +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_user.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 T-Systems MMS +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This module 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. +# +# This software 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 this software. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: mongodb_atlas_user +short_description: Manage database users in Atlas +description: + - The mongodb_atlas_user module lets you create, modify and delete the database users in your cluster. + - Each user has a set of roles that provide access to the project's databases. + - A user's roles apply to all the clusters in the project + - if two clusters have a products database and a user has a role granting read access on the products database, + - the user has that access on both clusters. + - L(API Documentation,https://docs.atlas.mongodb.com/reference/api/database-users/) +author: "Martin Schurz (@schurzi)" +extends_documentation_fragment: community.mongodb.atlas_options +options: + database_name: + description: + - Database against which Atlas authenticates the user. + choices: ["admin", "$external"] + default: "admin" + type: str + aliases: [ "databaseName" ] + username: + description: + - Username for authenticating to MongoDB. + required: true + type: str + password: + description: + - User's password. + required: true + type: str + roles: + description: + - Array of this user's roles and the databases / collections on which the roles apply. + - A role must include following elements + suboptions: + database_name: + required: true + type: str + description: + - Database on which the user has the specified role. + - A role on the admin database can include privileges that apply to the other databases. + aliases: [ "databaseName" ] + role_name: + required: true + type: str + description: + - Name of the role. This value can either be a built-in role or a custom role. + aliases: [ "roleName" ] + required: true + type: list + elements: dict + scopes: + description: + - List of clusters and Atlas Data Lakes that this user can access. + - Atlas grants database users access to all resources by default. + suboptions: + name: + required: true + type: str + description: + - Name of the cluster or Atlas Data Lake that the database user can access. + type: + type: str + choices: ["CLUSTER", "DATA_LAKE"] + default: "CLUSTER" + description: + - Type of resource that the database user can access. + required: false + default: [] + type: list + elements: dict +''' + +EXAMPLES = ''' + - name: test user + community.mongodb.mongodb_atlas_user: + api_username: "API_user" + api_password: "API_passwort_or_token" + group_id: "GROUP_ID" + username: my_app_user + password: SuperSecret! + roles: + - database_name: private_info + role_name: read + - database_name: public_info + role_name: readWrite +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_atlas import ( + AtlasAPIObject, +) + + +# =========================================== +# Module execution. +# +def main(): + # add our own arguments + argument_spec = dict( + state=dict(default="present", choices=["absent", "present"]), + api_username=dict(required=True, aliases=['apiUsername']), + api_password=dict(required=True, no_log=True, aliases=['apiPassword']), + group_id=dict(required=True, aliases=['groupId']), + database_name=dict(default="admin", choices=["admin", "$external"], aliases=["databaseName"]), + username=dict(required=True), + password=dict(required=True, no_log=True), + roles=dict( + required=True, + type="list", + elements="dict", + options=dict( + database_name=dict(required=True, aliases=["databaseName"]), + role_name=dict(required=True, aliases=["roleName"]), + ), + ), + scopes=dict( + required=False, + type="list", + elements="dict", + options=dict( + name=dict(required=True), + type=dict(default="CLUSTER", choices=["CLUSTER", "DATA_LAKE"]), + ), + default=[], + ), + ) + + # Define the main module + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + + data = { + "databaseName": module.params["database_name"], + "username": module.params["username"], + "password": module.params["password"], + "roles": [], + "scopes": module.params["scopes"], + } + + # remap keys to API format + for role in module.params.get("roles"): + data["roles"].append({ + "databaseName": role.get("database_name"), + "roleName": role.get("role_name") + }) + + try: + atlas = AtlasAPIObject( + module=module, + path="/databaseUsers", + object_name="username", + group_id=module.params["group_id"], + data=data, + ) + except Exception as e: + module.fail_json( + msg="unable to connect to Atlas API. Exception message: %s" % e + ) + + changed, diff = atlas.update(module.params["state"]) + module.exit_json( + changed=changed, + data=atlas.data, + diff=diff, + ) + + +# import module snippets +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_whitelist.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_whitelist.py new file mode 100644 index 000000000..5354321a6 --- /dev/null +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_atlas_whitelist.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 T-Systems MMS +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# This module 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. +# +# This software 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 this software. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: mongodb_atlas_whitelist +short_description: Manage IP whitelists in Atlas +description: + - The mongodb_atlas_whitelist module manages a Atlas project's IP whitelist. + - L(API Documentation,https://docs.atlas.mongodb.com/reference/api/whitelist/) +author: "Martin Schurz (@schurzi)" +extends_documentation_fragment: community.mongodb.atlas_options +options: + cidr_block: + description: + - Whitelist entry in Classless Inter-Domain Routing (CIDR) notation. + type: str + required: True + aliases: [ "cidrBlock" ] + comment: + description: + - Optional Comment associated with the whitelist entry. + type: str + default: "created by Ansible" +''' + +EXAMPLES = ''' + - name: test whitelist + community.mongodb.mongodb_atlas_whitelist: + api_username: "API_user" + api_password: "API_passwort_or_token" + group_id: "GROUP_ID" + cidr_block: "192.168.0.0/24" + comment: "test" +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_atlas import ( + AtlasAPIObject, +) + + +# =========================================== +# Module execution. +# +def main(): + # add our own arguments + argument_spec = dict( + state=dict(default="present", choices=["absent", "present"]), + api_username=dict(required=True, aliases=['apiUsername']), + api_password=dict(required=True, no_log=True, aliases=['apiPassword']), + group_id=dict(required=True, aliases=['groupId']), + cidr_block=dict(required=True, aliases=["cidrBlock"]), + comment=dict(default="created by Ansible"), + ) + + # Define the main module + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + + data = { + "cidrBlock": module.params["cidr_block"], + "comment": module.params["comment"], + } + + try: + atlas = AtlasAPIObject( + module=module, + path="/whitelist", + object_name="cidrBlock", + group_id=module.params["group_id"], + data=data, + data_is_array=True, + ) + except Exception as e: + module.fail_json( + msg="unable to connect to Atlas API. Exception message: %s" % e + ) + + changed, diff = atlas.update(module.params["state"]) + module.exit_json( + changed=changed, + data=atlas.data, + diff=diff, + ) + + +# import module snippets +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_balancer.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_balancer.py index 0e9b33a34..77ce37ed0 100644 --- a/ansible_collections/community/mongodb/plugins/modules/mongodb_balancer.py +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_balancer.py @@ -29,6 +29,8 @@ options: autosplit: description: - Disable or enable the autosplit flag in the config.settings collection. + - From MongoDB 6.1 automatic chunk splitting is not performed so this parameter is not valid in this and later versions. See more see [enableAutoSplit](https://www.mongodb.com/docs/manual/reference/method/sh.enableAutoSplit/). # noqa: E501 + - This parameter is deprecated and will be removed in a future release. required: false type: bool chunksize: diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_monitoring.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_monitoring.py deleted file mode 100644 index d399a9907..000000000 --- a/ansible_collections/community/mongodb/plugins/modules/mongodb_monitoring.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/python - -# Copyright: (c) 2021, Rhys Campbell rhyscampbell@blueiwn.ch -# 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 - - -DOCUMENTATION = r''' ---- -module: mongodb_monitoring -short_description: Manages the free monitoring feature. -description: - - Manages the free monitoring feature. - - Optionally return the monitoring url. -author: Rhys Campbell (@rhysmeister) -version_added: "1.3.0" - -extends_documentation_fragment: - - community.mongodb.login_options - - community.mongodb.ssl_options - -options: - state: - description: Manage the free monitoring feature. - type: str - choices: - - "started" - - "stopped" - default: "started" - return_url: - description: When true return the monitoring url if available. - type: bool - default: false - -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 U(http://api.mongodb.org/python/current/installation.html) -requirements: - - pymongo -''' - -EXAMPLES = r''' -- name: Enable monitoring - community.mongodb.mongodb_monitoring: - state: "started" - -- name: Disable monitoring - community.mongodb.mongodb_monitoring: - state: "stopped" - -- name: Enable monitoring and return the monitoring url - community.mongodb_monitoring: - state: "started" - return_url: "yes" -''' - -RETURN = r''' -changed: - description: Whether the monitoring status changed. - returned: success - type: bool -msg: - description: A short description of what happened. - returned: success - type: str -failed: - description: If something went wrong - returned: failed - type: bool -url: - description: The MongoDB instance Monitoring url. - returned: When requested and available. - type: str -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native -from ansible_collections.community.mongodb.plugins.module_utils.mongodb_common import ( - missing_required_lib, - mongodb_common_argument_spec, - PYMONGO_IMP_ERR, - pymongo_found, - mongo_auth, - get_mongodb_client, -) - -has_ordereddict = False -try: - from collections import OrderedDict - has_ordereddict = True -except ImportError as excep: - try: - from ordereddict import OrderedDict - has_ordereddict = True - except ImportError as excep: - pass - - -def stop_monitoring(client): - ''' - Stops MongoDB Free Monitoring - ''' - cmd_doc = OrderedDict([('setFreeMonitoring', 1), - ('action', 'disable')]) - client['admin'].command(cmd_doc) - - -def start_monitoring(client): - ''' - Stops MongoDB Free Monitoring - ''' - cmd_doc = OrderedDict([('setFreeMonitoring', 1), - ('action', 'enable')]) - client['admin'].command(cmd_doc) - - -def get_monitoring_status(client): - ''' - Gets the state of MongoDB Monitoring. - N.B. If Monitoring has never been enabled the - free_monitoring record in admin.system.version - will not yet exist. - ''' - monitoring_state = None - url = None - result = client["admin"]['system.version'].find_one({"_id": "free_monitoring"}) - if not result: - monitoring_state = "stopped" - else: - url = result["informationalURL"] - if result["state"] == "enabled": - monitoring_state = "started" - else: - monitoring_state = "stopped" - return monitoring_state, url - - -def main(): - argument_spec = mongodb_common_argument_spec() - argument_spec.update( - state=dict(type='str', default='started', choices=['started', 'stopped']), - return_url=dict(type='bool', default=False) - ) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - required_together=[['login_user', 'login_password']], - ) - - if not has_ordereddict: - module.fail_json(msg='Cannot import OrderedDict class. You can probably install with: pip install ordereddict') - - if not pymongo_found: - module.fail_json(msg=missing_required_lib('pymongo'), - exception=PYMONGO_IMP_ERR) - - state = module.params['state'] - return_url = module.params['return_url'] - - try: - client = get_mongodb_client(module, directConnection=True) - client = mongo_auth(module, client, directConnection=True) - except Exception as e: - module.fail_json(msg='Unable to connect to database: %s' % to_native(e)) - - current_monitoring_state, url = get_monitoring_status(client) - result = {} - if state == "started": - if current_monitoring_state == "started": - result['changed'] = False - result['msg'] = "Free monitoring is already started" - else: - if module.check_mode is False: - start_monitoring(client) - result['changed'] = True - result['msg'] = "Free monitoring has been started" - else: - if current_monitoring_state == "started": - if module.check_mode is False: - stop_monitoring(client) - result['changed'] = True - result['msg'] = "Free monitoring has been stopped" - else: - result['changed'] = False - result['msg'] = "Free monitoring is already stopped" - - if return_url and url: - result['url'] = url - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_replicaset.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_replicaset.py index d0baf661e..d6cdaae26 100644 --- a/ansible_collections/community/mongodb/plugins/modules/mongodb_replicaset.py +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_replicaset.py @@ -554,8 +554,11 @@ def main(): debug = module.params['debug'] cluster_cmd = module.params['cluster_cmd'] + # Count voting members + voting_members = sum([1 if not isinstance(m, dict) or m.get("votes", 1) == 1 else 0 for m in members]) + if validate and reconfigure is False: - if len(members) <= 2 or len(members) % 2 == 0: + if len(members) <= 2 or voting_members % 2 == 0: module.fail_json(msg="MongoDB Replicaset validation failed. Invalid number of replicaset members.") if arbiter_at_index is not None and len(members) - 1 < arbiter_at_index: module.fail_json(msg="MongoDB Replicaset validation failed. Invalid arbiter index.") diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_role.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_role.py index 012f553a0..23f653c32 100644 --- a/ansible_collections/community/mongodb/plugins/modules/mongodb_role.py +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_role.py @@ -303,8 +303,8 @@ def check_if_role_changed(client, role, db_name, privileges, authenticationRestr 'roles' not in role_dict and roles != []): changed = True elif ('authenticationRestrictions' in role_dict and - sorted(reformat_authenticationRestrictions, key=lambda x: (x['clientSource'], x['serverAddress'])) != - sorted(authenticationRestrictions, key=lambda x: (x['clientSource'], x['serverAddress'])) or + sorted(reformat_authenticationRestrictions, key=lambda x: (x.get('clientSource', ''), x.get('serverAddress', ''))) != + sorted(authenticationRestrictions, key=lambda x: (x.get('clientSource', ''), x.get('serverAddress', ''))) or 'authenticationRestrictions' not in role_dict and authenticationRestrictions != []): changed = True else: diff --git a/ansible_collections/community/mongodb/plugins/modules/mongodb_user.py b/ansible_collections/community/mongodb/plugins/modules/mongodb_user.py index eab0d186c..fee4c48c1 100644 --- a/ansible_collections/community/mongodb/plugins/modules/mongodb_user.py +++ b/ansible_collections/community/mongodb/plugins/modules/mongodb_user.py @@ -189,7 +189,6 @@ user: import os import traceback -from operator import itemgetter from ansible.module_utils.basic import AnsibleModule, missing_required_lib @@ -307,7 +306,7 @@ def check_if_roles_changed(uinfo, roles, db_name): 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, key=itemgetter('db')) == sorted(uinfo_roles, key=itemgetter('db')): + if sorted(roles_as_list_of_dict, key=lambda roles: sorted(roles.items())) == sorted(uinfo_roles, key=lambda roles: sorted(roles.items())): return False return True |