From 975f66f2eebe9dadba04f275774d4ab83f74cf25 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:04:41 +0200 Subject: Adding upstream version 7.7.0+dfsg. Signed-off-by: Daniel Baumann --- .../sensu/sensu_go/plugins/action/bonsai_asset.py | 120 ++++++ .../sensu_go/plugins/doc_fragments/annotations.py | 20 + .../sensu/sensu_go/plugins/doc_fragments/auth.py | 75 ++++ .../sensu/sensu_go/plugins/doc_fragments/info.py | 17 + .../sensu/sensu_go/plugins/doc_fragments/labels.py | 19 + .../sensu/sensu_go/plugins/doc_fragments/name.py | 25 ++ .../sensu_go/plugins/doc_fragments/namespace.py | 19 + .../sensu_go/plugins/doc_fragments/requirements.py | 16 + .../sensu_go/plugins/doc_fragments/secrets.py | 32 ++ .../sensu/sensu_go/plugins/doc_fragments/state.py | 19 + .../sensu/sensu_go/plugins/filter/backends.py | 28 ++ .../sensu/sensu_go/plugins/filter/package_name.py | 40 ++ .../sensu_go/plugins/module_utils/arguments.py | 120 ++++++ .../sensu/sensu_go/plugins/module_utils/bonsai.py | 67 +++ .../sensu/sensu_go/plugins/module_utils/client.py | 137 +++++++ .../sensu/sensu_go/plugins/module_utils/debug.py | 45 +++ .../sensu/sensu_go/plugins/module_utils/errors.py | 27 ++ .../sensu/sensu_go/plugins/module_utils/http.py | 71 ++++ .../sensu_go/plugins/module_utils/role_utils.py | 76 ++++ .../sensu/sensu_go/plugins/module_utils/utils.py | 176 ++++++++ .../sensu_go/plugins/modules/ad_auth_provider.py | 396 ++++++++++++++++++ .../sensu/sensu_go/plugins/modules/asset.py | 213 ++++++++++ .../sensu/sensu_go/plugins/modules/asset_info.py | 99 +++++ .../sensu_go/plugins/modules/auth_provider_info.py | 145 +++++++ .../sensu/sensu_go/plugins/modules/bonsai_asset.py | 131 ++++++ .../sensu/sensu_go/plugins/modules/check.py | 449 +++++++++++++++++++++ .../sensu/sensu_go/plugins/modules/check_info.py | 97 +++++ .../sensu/sensu_go/plugins/modules/cluster.py | 116 ++++++ .../sensu/sensu_go/plugins/modules/cluster_info.py | 101 +++++ .../sensu/sensu_go/plugins/modules/cluster_role.py | 164 ++++++++ .../plugins/modules/cluster_role_binding.py | 145 +++++++ .../plugins/modules/cluster_role_binding_info.py | 97 +++++ .../sensu_go/plugins/modules/cluster_role_info.py | 98 +++++ .../sensu/sensu_go/plugins/modules/datastore.py | 170 ++++++++ .../sensu_go/plugins/modules/datastore_info.py | 104 +++++ .../sensu/sensu_go/plugins/modules/entity.py | 261 ++++++++++++ .../sensu/sensu_go/plugins/modules/entity_info.py | 125 ++++++ .../sensu_go/plugins/modules/etcd_replicator.py | 218 ++++++++++ .../plugins/modules/etcd_replicator_info.py | 104 +++++ .../sensu/sensu_go/plugins/modules/event.py | 335 +++++++++++++++ .../sensu/sensu_go/plugins/modules/event_info.py | 142 +++++++ .../sensu/sensu_go/plugins/modules/filter.py | 158 ++++++++ .../sensu/sensu_go/plugins/modules/filter_info.py | 93 +++++ .../sensu/sensu_go/plugins/modules/handler_info.py | 94 +++++ .../sensu/sensu_go/plugins/modules/handler_set.py | 119 ++++++ .../sensu/sensu_go/plugins/modules/hook.py | 149 +++++++ .../sensu/sensu_go/plugins/modules/hook_info.py | 95 +++++ .../sensu_go/plugins/modules/ldap_auth_provider.py | 378 +++++++++++++++++ .../sensu/sensu_go/plugins/modules/mutator.py | 154 +++++++ .../sensu/sensu_go/plugins/modules/mutator_info.py | 96 +++++ .../sensu/sensu_go/plugins/modules/namespace.py | 87 ++++ .../sensu_go/plugins/modules/namespace_info.py | 79 ++++ .../sensu_go/plugins/modules/oidc_auth_provider.py | 248 ++++++++++++ .../sensu/sensu_go/plugins/modules/pipe_handler.py | 167 ++++++++ .../sensu/sensu_go/plugins/modules/role.py | 165 ++++++++ .../sensu/sensu_go/plugins/modules/role_binding.py | 176 ++++++++ .../sensu_go/plugins/modules/role_binding_info.py | 97 +++++ .../sensu/sensu_go/plugins/modules/role_info.py | 100 +++++ .../sensu/sensu_go/plugins/modules/secret.py | 131 ++++++ .../sensu/sensu_go/plugins/modules/secret_info.py | 110 +++++ .../plugins/modules/secrets_provider_env.py | 98 +++++ .../plugins/modules/secrets_provider_info.py | 111 +++++ .../plugins/modules/secrets_provider_vault.py | 258 ++++++++++++ .../sensu/sensu_go/plugins/modules/silence.py | 172 ++++++++ .../sensu/sensu_go/plugins/modules/silence_info.py | 112 +++++ .../sensu_go/plugins/modules/socket_handler.py | 160 ++++++++ .../sensu/sensu_go/plugins/modules/tessen.py | 108 +++++ .../sensu/sensu_go/plugins/modules/user.py | 326 +++++++++++++++ .../sensu/sensu_go/plugins/modules/user_info.py | 90 +++++ 69 files changed, 8990 insertions(+) create mode 100644 ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/filter/backends.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/filter/package_name.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/client.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/http.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/asset.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/check.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/check_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/cluster.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/datastore.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/entity.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/event.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/event_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/filter.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/hook.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/mutator.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/namespace.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/role.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/role_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/secret.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/silence.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/tessen.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/user.py create mode 100644 ansible_collections/sensu/sensu_go/plugins/modules/user_info.py (limited to 'ansible_collections/sensu/sensu_go/plugins') diff --git a/ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py b/ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py new file mode 100644 index 000000000..74473aeb7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/action/bonsai_asset.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +from ansible.module_utils.six import text_type +from ansible.plugins.action import ActionBase +from ansible.utils.vars import merge_hash + +from ..module_utils import bonsai, errors + + +def validate(name, args, required, typ): + """ + Make sure that required values are not None and that if the value is + present, it is of the correct type. + """ + value = args.get(name) + if required and value is None: + raise errors.Error("{0} is required argument".format(name)) + if value is not None and not isinstance(value, typ): + raise errors.Error("{0} should be {1}".format(name, typ)) + + +class ActionModule(ActionBase): + + _VALID_ARGS = frozenset(( + "auth", "name", "version", "namespace", "rename", "labels", + "annotations", "on_remote", + )) + + def run(self, _tmp=None, task_vars=None): + self._supports_check_mode = True + self._supports_async = True + + result = super(ActionModule, self).run(task_vars=task_vars) + + wrap_async = ( + self._task.async_val and not self._connection.has_native_async + ) + + try: + self.validate_arguments(self._task.args) + asset = self.download_asset_definition( + self._task.args.get("on_remote", False), + self._task.args["name"], + self._task.args["version"], + task_vars, + ) + asset_args = self.build_asset_args(self._task.args, asset) + return merge_hash( + result, + self._execute_module( + module_name="sensu.sensu_go.asset", module_args=asset_args, + task_vars=task_vars, wrap_async=wrap_async, + ), + ) + except errors.Error as e: + return dict(result, failed=True, msg=str(e)) + finally: + if not wrap_async: + self._remove_tmp_path(self._connection._shell.tmpdir) + + @staticmethod + def validate_arguments(args): + # We only validate arguments that we use. We let the asset module + # validate the rest (like auth data). + + # Next three string validations might seem strange at first, but there + # is a reason for this strangenes. On python 2, we should consider + # string to be instance of str or unicode. On python 3, strings are + # always instances of str. In order to avoid having a separate + # validate calls for python 2 and python 3, we always pass a pair of + # types that just happen to be the same on python 3. + validate("name", args, required=True, typ=(str, text_type)) + validate("version", args, required=True, typ=(str, text_type)) + validate("rename", args, required=False, typ=(str, text_type)) + validate("labels", args, required=False, typ=dict) + validate("annotations", args, required=False, typ=dict) + validate("on_remote", args, required=False, typ=bool) + + def download_asset_definition(self, on_remote, name, version, task_vars): + if not on_remote: + return bonsai.get_asset_parameters(name, version) + + args = dict(name=name, version=version) + result = self._execute_module( + module_name="sensu.sensu_go.bonsai_asset", module_args=args, + task_vars=task_vars, wrap_async=False, + ) + if result.get("failed", False): + raise errors.Error(result["msg"]) + + return result["asset"] + + @staticmethod + def build_asset_args(args, bonsai_args): + asset_args = dict( + name=args.get("rename", args["name"]), + state="present", + builds=bonsai_args["builds"], + ) + + if "auth" in args: + asset_args["auth"] = args["auth"] + + if "namespace" in args: + asset_args["namespace"] = args["namespace"] + + # Only add optional parameter if it is present in at least one source. + for meta in ("labels", "annotations"): + if bonsai_args[meta] or args.get(meta): + asset_args[meta] = merge_hash( + bonsai_args[meta] or {}, args.get(meta, {}), + ) + + return asset_args diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py new file mode 100644 index 000000000..3e93c9484 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/annotations.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + annotations: + description: + - Custom metadata fields with fewer restrictions, as key/value pairs. + - These are preserved by Sensu but not accessible as tokens or + identifiers, and are mainly intended for use with external tools. + type: dict + default: {} +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py new file mode 100644 index 000000000..4fe8aee04 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/auth.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + auth: + description: + - Authentication parameters. Can define each of them with ENV as well. + type: dict + suboptions: + user: + description: + - The username to use for connecting to the Sensu API. + If this is not set the value of the SENSU_USER environment + variable will be checked. + - This parameter is ignored if the I(auth.api_key) parameter is set. + type: str + default: admin + password: + description: + - The Sensu user's password. + If this is not set the value of the SENSU_PASSWORD environment + variable will be checked. + - This parameter is ignored if the I(auth.api_key) parameter is set. + type: str + default: P@ssw0rd! + url: + description: + - Location of the Sensu backend API. + If this is not set the value of the SENSU_URL environment variable + will be checked. + type: str + default: http://localhost:8080 + api_key: + description: + - The API key that should be used when authenticating. If this is + not set, the value of the SENSU_API_KEY environment variable will + be checked. + - This replaces I(auth.user) and I(auth.password) parameters. + - For more information about the API key, refer to the official + Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/guides/use-apikey-feature/). + type: str + version_added: 1.3.0 + verify: + description: + - Flag that controls the certificate validation. + - If you are using self-signed certificates, you can set this + parameter to C(false). + - ONLY USE THIS PARAMETER IN DEVELOPMENT SCENARIOS! In you use + self-signed certificates in production, see the I(auth.ca_path) + parameter. + - It is also possible to set this parameter via the I(SENSU_VERIFY) + environment variable. + type: bool + default: true + version_added: 1.5.0 + ca_path: + description: + - Path to the CA bundle that should be used to validate the backend + certificate. + - If this parameter is not set, module will use the CA bundle that + python is using. + - It is also possible to set this parameter via the I(SENSU_CA_PATH) + environment variable. + type: path + version_added: 1.5.0 +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py new file mode 100644 index 000000000..326143684 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/info.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + name: + description: + - Retrieve information about this specific object instead of listing all objects. + type: str +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py new file mode 100644 index 000000000..849cd85df --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/labels.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + labels: + description: + - Custom metadata fields that can be accessed within Sensu, as key/value + pairs. + type: dict + default: {} +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py new file mode 100644 index 000000000..68efc27a8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/name.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + name: + description: + - The Sensu resource's name. This name (in combination with the + namespace where applicable) uniquely identifies the resource that + Ansible operates on. + - If the resource with selected name already exists, Ansible module will + update it to match the specification in the task. + - Consult the I(name) metadata attribute specification in the upstream + docs on U(https://docs.sensu.io/sensu-go/latest/reference/) for + more details about valid names and other restrictions. + type: str + required: yes +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py new file mode 100644 index 000000000..169a1ee0b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/namespace.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + namespace: + description: + - RBAC namespace to operate in. If this is not set the value of the + SENSU_NAMESPACE environment variable will be used. + type: str + default: default +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py new file mode 100644 index 000000000..15ca542bc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/requirements.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + # We have an empty options key because ansible fails without it. + DOCUMENTATION = """ +options: {} +requirements: + - python >= 2.7 +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py new file mode 100644 index 000000000..44c48145b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/secrets.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + secrets: + description: + - List of secrets that are available to the command. + type: list + elements: dict + version_added: 1.6.0 + suboptions: + name: + description: + - Variable name that will contain the sensitive data. + type: str + required: true + version_added: 1.6.0 + secret: + description: + - Name of the secret that contains sensitive data. + type: str + required: true + version_added: 1.6.0 +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py new file mode 100644 index 000000000..513c20650 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/doc_fragments/state.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class ModuleDocFragment(object): + DOCUMENTATION = """ +options: + state: + description: + - Target state of the Sensu object. + type: str + choices: [ present, absent ] + default: present +""" diff --git a/ansible_collections/sensu/sensu_go/plugins/filter/backends.py b/ansible_collections/sensu/sensu_go/plugins/filter/backends.py new file mode 100644 index 000000000..ecafd458f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/filter/backends.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +def _format_backend(vars): + if "api_key_file" in vars: + protocol = "wss" + else: + protocol = "ws" + return "{0}://{1}:{2}".format(protocol, vars["inventory_hostname"], 8081) + + +def backends(hostvars, groups): + return [ + _format_backend(hostvars[name]) for name in groups.get("backends", []) + ] + + +class FilterModule(object): + def filters(self): + return dict( + backends=backends, + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/filter/package_name.py b/ansible_collections/sensu/sensu_go/plugins/filter/package_name.py new file mode 100644 index 000000000..80e9a8750 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/filter/package_name.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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 + + +def _apt_package_name(name, version, build): + if version == "latest": + return name + if build == "latest": + return "{0}={1}-*".format(name, version) + return "{0}={1}-{2}".format(name, version, build) + + +def _yum_package_name(name, version, build): + if version == "latest": + return name + if build == "latest": + return "{0}-{1}".format(name, version) + return "{0}-{1}-{2}".format(name, version, build) + + +KIND_HANDLERS = dict( + apt=_apt_package_name, + yum=_yum_package_name, +) + + +def package_name(kind, name, version, build): + return KIND_HANDLERS[kind](name, version, build) + + +class FilterModule(object): + def filters(self): + return dict( + package_name=package_name, + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py new file mode 100644 index 000000000..aed632dcf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/arguments.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +from ansible.module_utils.basic import env_fallback + +from . import client + + +SHARED_SPECS = dict( + auth=dict( + type="dict", + apply_defaults=True, + options=dict( + user=dict( + default="admin", + fallback=(env_fallback, ["SENSU_USER"]), + ), + password=dict( + default="P@ssw0rd!", + no_log=True, + fallback=(env_fallback, ["SENSU_PASSWORD"]), + ), + url=dict( + default="http://localhost:8080", + fallback=(env_fallback, ["SENSU_URL"]), + ), + api_key=dict( + fallback=(env_fallback, ["SENSU_API_KEY"]), + no_log=True, + ), + verify=dict( + default=True, + fallback=(env_fallback, ["SENSU_VERIFY"]), + type="bool", + ), + ca_path=dict( + fallback=(env_fallback, ["SENSU_CA_PATH"]), + type="path", + ), + ), + ), + state=dict( + default="present", + choices=["present", "absent"], + ), + name=dict( + required=True, + ), + namespace=dict( + default="default", + fallback=(env_fallback, ["SENSU_NAMESPACE"]), + ), + labels=dict( + type="dict", + default={}, + ), + annotations=dict( + type="dict", + default={}, + ), + secrets=dict( + type="list", + elements="dict", + no_log=False, + options=dict( + name=dict(type="str", required=True), + secret=dict(type="str", required=True, no_log=False), + ), + ), +) + + +def get_spec(*param_names): + return dict((p, SHARED_SPECS[p]) for p in param_names) + + +def get_spec_payload(source, *wanted_params): + return dict( + (k, source[k]) for k in wanted_params if source.get(k) is not None + ) + + +def get_renamed_spec_payload(source, param_mapping): + return dict( + (n, source[k]) for k, n in param_mapping.items() + if source.get(k) is not None + ) + + +def get_mutation_payload(source, *wanted_params): + payload = get_spec_payload(source, *wanted_params) + payload["metadata"] = dict( + name=source["name"], + ) + # Cluster-wide objects are not limited to a single namespace. This is why we set + # metadata.namespace field only if namespace is present in parameters. + if "namespace" in source: + if not source["namespace"]: + # We are raising an exception here for the sake of sanity test. + raise AssertionError("BUG: namespace should not be None") + payload["metadata"]["namespace"] = source["namespace"] + + for kind in "labels", "annotations": + if source.get(kind): + payload["metadata"][kind] = dict( + (k, str(v)) for k, v in source[kind].items() + ) + return payload + + +def get_sensu_client(auth): + return client.Client( + auth["url"], auth["user"], auth["password"], auth["api_key"], + auth["verify"], auth["ca_path"], + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py new file mode 100644 index 000000000..b7962cf85 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/bonsai.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +from . import errors, http + + +def get(path): + url = "https://bonsai.sensu.io/api/v1/assets/{0}".format(path) + resp = http.request("GET", url) + + if resp.status != 200: + raise errors.BonsaiError( + "Server returned status {0}".format(resp.status), + ) + if resp.json is None: + raise errors.BonsaiError("Server returned invalid JSON document") + + return resp.json + + +def get_available_asset_versions(namespace, name): + asset_data = get("{0}/{1}".format(namespace, name)) + try: + return set(v["version"] for v in asset_data["versions"]) + except (TypeError, KeyError): + raise errors.BonsaiError( + "Cannot extract versions from {0}".format(asset_data), + ) + + +def get_asset_version_builds(namespace, name, version): + asset = get("{0}/{1}/{2}/release_asset_builds".format( + namespace, name, version, + )) + if "spec" not in asset or "builds" not in asset["spec"]: + raise errors.BonsaiError("Invalid build spec: {0}".format(asset)) + return asset + + +def get_asset_parameters(name, version): + try: + namespace, asset_name = name.split("/") + except ValueError: + raise errors.BonsaiError( + "Bonsai asset names should be formatted as /.", + ) + + available_versions = get_available_asset_versions(namespace, asset_name) + if version not in available_versions: + raise errors.BonsaiError( + "Version {0} is not available. Choose from: {1}.".format( + version, ", ".join(available_versions), + ), + ) + + asset_builds = get_asset_version_builds(namespace, asset_name, version) + + return dict( + labels=asset_builds.get("metadata", {}).get("labels"), + annotations=asset_builds.get("metadata", {}).get("annotations"), + builds=asset_builds["spec"]["builds"], + ) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/client.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/client.py new file mode 100644 index 000000000..cd53077d3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/client.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +try: + from ansible.module_utils.compat import version +except ImportError: + from distutils import version + +from . import errors, http + + +class Client: + BAD_VERSION = version.StrictVersion("9999.99.99") + + def __init__(self, address, username, password, api_key, verify, ca_path): + self.address = address.rstrip("/") + self.username = username + self.password = password + self.api_key = api_key + self.verify = verify + self.ca_path = ca_path + + self._auth_header = None # Login when/if required + self._version = None # Set version only if the consumer needs it + + @property + def auth_header(self): + if not self._auth_header: + self._auth_header = self._login() + return self._auth_header + + @property + def version(self): + if self._version is None: + resp = self.get("/version") + if resp.status != 200: + raise errors.SensuError( + "Version API returned status {0}".format(resp.status), + ) + if resp.json is None: + raise errors.SensuError( + "Version API did not return a valid JSON", + ) + if "sensu_backend" not in resp.json: + raise errors.SensuError( + "Version API did not return backend version", + ) + try: + self._version = version.StrictVersion( + resp.json["sensu_backend"].split("#")[0] + ) + except ValueError: + # Backend has no version compiled in - we are probably running + # againts self-compiled version from git. + self._version = self.BAD_VERSION + + return self._version + + def _login(self): + if self.api_key: + return self._api_key_login() + return self._username_password_login() + + def _api_key_login(self): + # We cannot validate the API key because there is no API endpoint that + # we could hit for verification purposes. This means that the error + # reporting will be a mess but there is not much we can do here. + return dict(Authorization="Key {0}".format(self.api_key)) + + def _username_password_login(self): + resp = http.request( + "GET", "{0}/auth".format(self.address), force_basic_auth=True, + url_username=self.username, url_password=self.password, + validate_certs=self.verify, ca_path=self.ca_path, + ) + + if resp.status != 200: + raise errors.SensuError( + "Authentication call returned status {0}".format(resp.status), + ) + + if resp.json is None: + raise errors.SensuError( + "Authentication call did not return a valid JSON", + ) + + if "access_token" not in resp.json: + raise errors.SensuError( + "Authentication call did not return access token", + ) + + return dict( + Authorization="Bearer {0}".format(resp.json["access_token"]), + ) + + def request(self, method, path, payload=None): + url = self.address + path + headers = self.auth_header + + response = http.request( + method, url, payload=payload, headers=headers, + validate_certs=self.verify, ca_path=self.ca_path, + ) + + if response.status in (401, 403): + raise errors.SensuError( + "Authentication problem. Verify your credentials." + ) + + return response + + def get(self, path): + return self.request("GET", path) + + def put(self, path, payload): + return self.request("PUT", path, payload) + + def delete(self, path): + return self.request("DELETE", path) + + def validate_auth_data(self, username, password): + resp = http.request( + "GET", "{0}/auth/test".format(self.address), + force_basic_auth=True, url_username=username, + url_password=password, validate_certs=self.verify, + ca_path=self.ca_path, + ) + if resp.status not in (200, 401): + raise errors.SensuError( + "Authentication test returned status {0}".format(resp.status), + ) + return resp.status == 200 diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py new file mode 100644 index 000000000..25bee3887 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/debug.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +import os +import tempfile +from datetime import datetime + + +DEBUG = os.environ.get("SENSU_ANSIBLE_DEBUG", "").lower() in ["yes", "true"] + + +def log(message, *args, **kwargs): + """ + Log message to a file (/tmp/sensu-ansible.log) at remote target + + Sensu API returns fairly modest error messages (e.g. when PUT payload contains + unsupported parameter, the error message won't tell you which one) and that + makes it difficult to debug. For that reason we decided to support at least + the most primitive type of logging: write to /tmp/sensu-ansible.log file. + Beware the log file resides on Ansible target and not host because this is + where the module gets executed. + + This function won't do anything unless target has environment variable + SENSU_ANSIBLE_DEBUG set to "yes". When troubleshooting, just set the env + variable in the playbook. + """ + if DEBUG: + with open(os.path.join(tempfile.gettempdir(), "sensu-ansible.log"), "a") as f: + f.write("[{0}]: {1}\n".format(datetime.utcnow(), message.format(*args, **kwargs))) + + +def log_request(method, url, payload, resp=None, comment=None): + """Log API request and response""" + if DEBUG: + if resp: + code, data = resp.status, resp.data + else: + code = data = "?" + fmt = "{0} {1} {2}\nPAYLOAD:{3}\nRESPONSE:{4}\nCOMMENT:{5}" + log(fmt, code, method, url, payload, data, comment) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py new file mode 100644 index 000000000..5be0675c7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/errors.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + + +class Error(Exception): + """ Base error that serves as a parent for all other errors. """ + + +class HttpError(Error): + """ Error that signals failure in HTTP connection. """ + + +class SyncError(Error): + """ Error that signals failure when syncing state with remote. """ + + +class SensuError(Error): + """ Error that signals problems with Sensu Go web API. """ + + +class BonsaiError(Error): + """ Error that signals problems with Bonsai assets. """ diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/http.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/http.py new file mode 100644 index 000000000..054fa7663 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/http.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +import json + +try: + from ssl import CertificateError +except ImportError: + # This will never match the ssl exception, which will cause exception to + # bubble up the call stack. + class CertificateError(Exception): + pass + +from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError +from ansible.module_utils.urls import open_url + +from . import errors, debug + + +class Response: + def __init__(self, status, data): + self.status = status + self.data = data + self._json = None + + @property + def json(self): + if self._json is None: + try: + self._json = json.loads(self.data) + except TypeError: # Handle python 3.5 returning bytes + try: + self._json = json.loads(self.data.decode('utf-8')) + except ValueError: + self._json = None + except ValueError: # Cannot use JSONDecodeError here (python 2) + self._json = None + + return self._json + + +def request(method, url, payload=None, data=None, headers=None, **kwargs): + if payload is not None: + data = json.dumps(payload, separators=(",", ":")) + headers = dict(headers or {}, **{"content-type": "application/json"}) + + try: + raw_resp = open_url( + method=method, url=url, data=data, headers=headers, **kwargs + ) + resp = Response(raw_resp.getcode(), raw_resp.read()) + debug.log_request(method, url, payload, resp) + return resp + except HTTPError as e: + # This is not an error, since client consumers might be able to + # work around/expect non 20x codes. + resp = Response(e.code, e.reason) + debug.log_request(method, url, payload, resp) + return resp + except URLError as e: + debug.log_request(method, url, payload, comment=e.reason) + raise errors.HttpError( + "{0} request failed: {1}".format(method, e.reason), + ) + except CertificateError as e: + raise errors.HttpError("Certificate error: {0}".format(e)) diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py new file mode 100644 index 000000000..4079473fb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/role_utils.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +from . import utils + + +def validate_module_params(params): + if params['state'] == 'present': + if not params['rules']: + return 'state is present but all of the following are missing: rules' + return None + + +def validate_binding_module_params(params): + if params["state"] == "present": + if not (params["users"] or params["groups"]): + return 'missing required parameters: users or groups' + + +def type_name_dict(obj_type, name): + return { + 'type': obj_type, + 'name': name, + } + + +def build_subjects(groups, users): + groups_dicts = [type_name_dict('Group', g) for g in (groups or [])] + users_dicts = [type_name_dict('User', u) for u in (users or [])] + + return groups_dicts + users_dicts + + +def do_role_bindings_differ(current, desired): + if _do_subjects_differ(current['subjects'], desired['subjects']): + return True + + return utils.do_differ(current, desired, 'subjects') + + +# sorts a list of subjects (dicts returned by type_name_dict) +# by 'type' and 'name' keys and returns the result of comparison. +def _do_subjects_differ(a, b): + sorted_a = sorted(a, key=lambda x: (x['type'], x['name'])) + sorted_b = sorted(b, key=lambda x: (x['type'], x['name'])) + return sorted_a != sorted_b + + +def _rule_set(rules): + return set( + ( + frozenset(r.get('verbs', []) or []), + frozenset(r.get('resources', []) or []), + frozenset(r.get('resource_names', []) or []) + ) for r in rules + ) + + +def _do_rules_differ(current_rules, desired_rules): + if len(current_rules) != len(desired_rules): + return True + if _rule_set(current_rules) != _rule_set(desired_rules): + return True + return False + + +def do_roles_differ(current, desired): + if _do_rules_differ(current['rules'], desired['rules']): + return True + + return utils.do_differ(current, desired, 'rules') diff --git a/ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py b/ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py new file mode 100644 index 000000000..bf97120f1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/module_utils/utils.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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 + +from ansible.module_utils.six.moves.urllib.parse import quote + +from . import errors + + +def do_differ(current, desired, *ignored_keys): + if "metadata" in desired: + # We treat metadata as any regular dict but ignore the created_by + # value since this piece of data is autogenerated on the backend and + # must be skipped in the comparison. + if do_differ(current["metadata"], desired["metadata"], "created_by"): + return True + + ignored_keys = ignored_keys + ("metadata",) + + for key, value in desired.items(): + if key in ignored_keys: + continue + + if value != current.get(key): + return True + + return False + + +def do_differ_v1(current, desired, *ignored_keys): + # Default comparator for v1 API (mostly enterprise features) + + if "metadata" in desired: + # We treat metadata as any regular dict but ignore the created_by + # value since this piece of data is autogenerated on the backend and + # must be skipped in the comparison. + if do_differ(current["metadata"], desired["metadata"], "created_by"): + return True + + for key, value in desired.get("spec", {}).items(): + if key in ignored_keys: + continue + + if value != current["spec"].get(key): + return True + + return False + + +def sync(state, client, path, payload, check_mode, compare=do_differ): + remote_object = get(client, path) + + if state == "absent" and remote_object is None: + return False, None + + if state == "absent": + if not check_mode: + delete(client, path) + return True, None + + # Making sure remote_object is present from here on + + if remote_object is None or compare(remote_object, payload): + if check_mode: + return True, payload + put(client, path, payload) + return True, get(client, path) + + return False, remote_object + + +def sync_v1(state, client, path, payload, check_mode, compare=do_differ_v1): + changed, result = sync(state, client, path, payload, check_mode, compare) + return changed, convert_v1_to_v2_response(result) + + +def _abort(msg, *args, **kwargs): + raise errors.SyncError(msg.format(*args, **kwargs)) + + +def get(client, path): + resp = client.get(path) + if resp.status not in (200, 404): + _abort( + "GET {0} failed with status {1}: {2}", path, resp.status, resp.data, + ) + if resp.status == 200 and resp.json is None: + _abort("Server returned invalid JSON {0}", resp.data) + return resp.json + + +def delete(client, path): + resp = client.delete(path) + if resp.status != 204: + _abort( + "DELETE {0} failed with status {1}: {2}", + path, resp.status, resp.data, + ) + return None + + +def put(client, path, payload): + resp = client.put(path, payload) + if resp.status not in (200, 201): + _abort( + "PUT {0} failed with status {1}: {2}", + path, resp.status, resp.data, + ) + return None + + +def dict_to_single_item_dicts(data): + return [{k: v} for k, v in data.items()] + + +def single_item_dicts_to_dict(data): + result = {} + for item in data: + (k, v), = item.items() + result[k] = v + return result + + +def dict_to_key_value_strings(data): + return ["{0}={1}".format(k, v) for k, v in data.items()] + + +def build_url_path(api_group, api_version, namespace, *parts): + prefix = "/api/{0}/{1}/".format(api_group, api_version) + if namespace: + prefix += "namespaces/{0}/".format(quote(namespace, safe="")) + return prefix + "/".join(quote(p, safe="") for p in parts if p) + + +def build_core_v2_path(namespace, *parts): + return build_url_path("core", "v2", namespace, *parts) + + +def prepare_result_list(result): + if isinstance(result, list): + return result + return [] if result is None else [result] + + +def convert_v1_to_v2_response(response): + # dict(metadata=, spec=dict(a=1)) -> dict(metadata=, a=1) + + if not response: + return response + + if "metadata" not in response: + return response["spec"] + + # Move metadata key into the spec. + return dict(response["spec"], metadata=response["metadata"]) + + +def do_secrets_differ(current, desired): + return set( + (c["name"], c["secret"]) for c in (current.get("secrets") or []) + ) != set( + (d["name"], d["secret"]) for d in (desired.get("secrets") or []) + ) + + +def deprecate(module, msg, version): + try: + module.deprecate(msg, version=version, collection_name="sensu.sensu_go") + except TypeError: + # Ansible < 2.9.10 does not support collection_name kwarg. Output at + # least msg and version of collection when things will stop working. + module.deprecate(msg, version=version) diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py b/ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py new file mode 100644 index 000000000..609b16cb9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/ad_auth_provider.py @@ -0,0 +1,396 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: ad_auth_provider + +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: Manage Sensu AD authentication provider + +description: + - Create, update or delete a Sensu Go AD authentication provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/ad-auth/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state + +options: + servers: + description: + - An array of AD servers for your directory. + type: list + elements: dict + suboptions: + host: + description: + - AD server IP address. + required: true + type: str + port: + description: + - AD server port. + type: int + insecure: + description: + - Skips SSL certificate verification when set to true. + type: bool + default: false + security: + description: + - Encryption type to be used for the connection to the AD server. + type: str + choices: [ insecure, tls, starttls ] + default: tls + trusted_ca_file: + description: + - Path to an alternative CA bundle file. + type: str + client_cert_file: + description: + - Path to the certificate that should be sent to the server if requested. + type: str + client_key_file: + description: + - Path to the key file associated with the client_cert_file. + - Required if I(client_cert_file) is present. + type: str + default_upn_domain: + description: + - Enables UPN authentication when set. The default UPN suffix that will be appended + to the username when a domain is not specified during login + (for example, user becomes user@defaultdomain.xyz). + type: str + include_nested_groups: + description: + - If true, the group search includes any nested groups a user is a member of. + If false, the group search includes only the top-level groups a user is a member of. + type: bool + binding: + description: + - The AD account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit the user_dn or password + attributes to query the directory without credentials. + type: dict + suboptions: + user_dn: + description: + - The AD account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + password: + description: + - Password for the user_dn account. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + group_search: + description: + - Search configuration for groups. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: member + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: cn + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: group + user_search: + description: + - Search configuration for users. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: sAMAccountName + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: displayName + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: person + groups_prefix: + description: + - The prefix added to all AD groups. + type: str + username_prefix: + description: + - The prefix added to all AD usernames. + type: str + +seealso: + - module: sensu.sensu_go.auth_provider_info + - module: sensu.sensu_go.ldap_auth_provider + - module: sensu.sensu_go.oidc_auth_provider +""" + +EXAMPLES = """ +- name: Create a AD auth provider + sensu.sensu_go.ad_auth_provider: + name: activedirectory + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org +- name: Delete a AD auth provider + sensu.sensu_go.ad_auth_provider: + name: activedirectory + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu AD authentication provider. + returned: success + type: dict + sample: + metadata: + name: 'activedirectory' + servers: + host: '127.0.0.1' + port: '636' + insecure: 'False' + security: 'tls' + trusted_ca_file: '/path/to/trusted-certificate-authorities.pem' + client_cert_file: '/path/to/ssl/cert.pem' + client_key_file: '/path/to/ssl/key.pem' + default_upn_domain: 'example.org' + binding: + user_dn: 'cn=binder,dc=acme,dc=org' + group_search: + base_dn: 'dc=acme,dc=org' + attribute: 'member' + name_attribute': 'cn' + object_class: 'group' + user_search: + base_dn: 'dc=acme,dc=org' + attribute: 'sAMAccountName' + name_attribute: 'displayName' + object_class: 'person' + groups_prefix: 'AD' + username_prefix: 'AD' +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + if result: + for server in result["servers"]: + if server["binding"] and "password" in server["binding"]: + del server["binding"]["password"] + + return result + + +def _filter(payload): + # Remove keys with None values from dict + return dict((k, v) for k, v in payload.items() if v is not None) + + +def do_differ(current, desired): + if utils.do_differ_v1(current, desired, "servers"): + return True + + if len(current["spec"]["servers"]) != len(desired["spec"]["servers"]): + return True + + for c, d in zip(current["spec"]["servers"], desired["spec"]["servers"]): + if utils.do_differ(c, _filter(d)): + return True + + return False + + +def main(): + required_if = [("state", "present", ["servers"])] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", + "name", + "state", + ), + servers=dict( + type="list", + elements="dict", + options=dict( + host=dict( + type="str", + required=True, + ), + port=dict( + type="int", + ), + insecure=dict( + type="bool", + default=False, + ), + security=dict( + type="str", + choices=["insecure", "tls", "starttls"], + default="tls", + ), + trusted_ca_file=dict( + type="str", + ), + client_cert_file=dict( + type="str", + ), + client_key_file=dict( + type="str", + ), + default_upn_domain=dict( + type="str", + ), + include_nested_groups=dict( + type="bool", + ), + binding=dict( + type="dict", + options=dict( + user_dn=dict( + type="str", + required=True, + ), + password=dict( + type="str", + no_log=True, + required=True, + ), + ), + ), + group_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="member", + ), + name_attribute=dict( + type="str", + default="cn", + ), + object_class=dict(type="str", default="group"), + ), + ), + user_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="sAMAccountName", + ), + name_attribute=dict( + type="str", + default="displayName", + ), + object_class=dict( + type="str", + default="person", + ), + ), + ), + ), + ), + groups_prefix=dict( + type="str", + ), + username_prefix=dict( + type="str", + ), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "authproviders", module.params["name"] + ) + + payload = dict( + type="ad", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload( + module.params, "servers", "groups_prefix", "username_prefix" + ), + ) + + try: + changed, ad_provider = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=remove_item(ad_provider)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/asset.py b/ansible_collections/sensu/sensu_go/plugins/modules/asset.py new file mode 100644 index 000000000..627bfabb3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/asset.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Cameron Hurst +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: asset +author: + - Cameron Hurst (@wakemaster39) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu assets +description: + - Create, update or delete Sensu Go asset. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/assets/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.asset_info + - module: sensu.sensu_go.bonsai_asset +options: + builds: + description: + - A list of asset builds used to define multiple artefacts which + provide the named asset. + - Required if I(state) is C(present). + type: list + elements: dict + suboptions: + url: + description: + - The URL location of the asset. + type: str + required: yes + sha512: + description: + - The checksum of the asset. + type: str + required: yes + filters: + description: + - A set of Sensu query expressions used to determine if the asset + should be installed. + type: list + elements: str + headers: + description: + - Additional headers to send when retrieving the asset, e.g. for + authorization. + type: dict +""" + +EXAMPLES = """ +- name: Create a multiple-build asset + sensu.sensu_go.asset: + name: sensu-plugins-cpu-checks + builds: + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_centos_linux_amd64.tar.gz + sha512: 518e7c17cf670393045bff4af318e1d35955bfde166e9ceec2b469109252f79043ed133241c4dc96501b6636a1ec5e008ea9ce055d1609865635d4f004d7187b + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + - entity.system.platform == 'rhel' + - url: https://assets.bonsai.sensu.io/68546e739d96fd695655b77b35b5aabfbabeb056/sensu-plugins-cpu-checks_4.0.0_alpine_linux_amd64.tar.gz + sha512: b2da25ecd7642e6de41fde37d674fe19dcb6ee3d680e145e32289f7cfc352e6b5f9413ee9b701d61faeaa47b399aa30b25885dbc1ca432c4061c8823774c28f3 + filters: + - entity.system.os == 'linux' + - entity.system.arch == 'amd64' + - entity.system.platform == 'alpine' + +- name: Delete an asset + sensu.sensu_go.asset: + name: sensu-plugins-cpu-check + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu asset. + returned: success + type: dict + sample: + metadata: + name: check_script + namespace: default + builds: + - sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b + url: http://example.com/asset.tar.gz +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def validate_module_params(params): + if params["state"] == "present": + if not params['builds']: + return "builds must include at least one element" + return None + + +def _build_set(builds): + return set(( + b.get('sha512'), + b.get('url'), + frozenset((b.get('headers', {}) or {}).items()), + frozenset(b.get('filters', []) or []), + ) for b in builds) + + +def _do_builds_differ(current, desired): + # Since Sensu Go 5.16, the web API returns builds: None if the asset + # in question is a deprecated, single-build asset. + if current is None: + return True + + if len(current) != len(desired): + return True + + return _build_set(current) != _build_set(desired) + + +def do_differ(current, desired): + if _do_builds_differ(current['builds'], desired['builds']): + return True + + return utils.do_differ(current, desired, 'builds') + + +def build_api_payload(params): + payload = arguments.get_mutation_payload(params) + if params['state'] == 'present': + builds = [arguments.get_spec_payload(b, *b.keys()) for b in params['builds']] + payload["builds"] = builds + return payload + + +def main(): + required_if = [ + ("state", "present", ["builds"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "namespace", "state", "labels", "annotations", + ), + builds=dict( + type="list", + elements="dict", + options=dict( + url=dict( + required=True, + ), + sha512=dict( + required=True, + ), + filters=dict( + type="list", + elements="str", + ), + headers=dict( + type="dict", + ), + ) + ), + ), + ) + + msg = validate_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "assets", module.params["name"], + ) + payload = build_api_payload(module.params) + + try: + changed, asset = utils.sync( + module.params["state"], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=asset) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py new file mode 100644 index 000000000..3c128dcb4 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/asset_info.py @@ -0,0 +1,99 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: asset_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu assets +description: + - Retrieve information about Sensu Go assets. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/assets/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.asset + - module: sensu.sensu_go.bonsai_asset +""" + +EXAMPLES = """ +- name: List all Sensu assets + sensu.sensu_go.asset_info: + register: result + +- name: List the selected Sensu asset + sensu.sensu_go.asset_info: + name: my_asset + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.metadata.name }}" + +""" + +RETURN = """ +objects: + description: List of Sensu assets. + returned: success + type: list + elements: dict + sample: + - metadata: + name: check_script + namespace: default + builds: + - sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b + url: http://example.com/asset.tar.gz +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "assets", module.params["name"], + ) + + try: + assets = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=assets) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py new file mode 100644 index 000000000..6215af445 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/auth_provider_info.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: auth_provider_info + +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: List Sensu authentication providers + +description: + - Retrieve information about Sensu Go authentication providers. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + +seealso: + - module: sensu.sensu_go.ad_auth_provider + - module: sensu.sensu_go.ldap_auth_provider + - module: sensu.sensu_go.oidc_auth_provider +""" + +EXAMPLES = """ +- name: List all Sensu authentication providers + sensu.sensu_go.auth_provider_info: + register: result + +- name: List the selected Sensu authentication provider + sensu.sensu_go.auth_provider_info: + name: my_auth_provider + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.metadata.name }}" +""" + +RETURN = """ +objects: + description: List of Sensu authentication providers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: 'openldap' + groups_prefix: '' + servers: + binding: + user_dn: 'cn=binder,dc=acme,dc=org' + client_cert_file: '' + client_key_file: '' + default_upn_domain: '' + group_search: + attribute: 'member' + base_dn: 'dc=acme,dc=org' + name_attribute: 'cn' + object_class: 'groupOfNames' + host: '127.0.0.1' + insecure: false + port: 636 + security: 'tls' + trusted_ca_file: '' + user_search: + attribute: 'uid' + base_dn: 'dc=acme,dc=org' + name_attribute: 'cn' + object_class: 'person' + username_prefix: '' +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + for server in result.get("servers", []): + if server["binding"] and "password" in server["binding"]: + del server["binding"]["password"] + + if "client_secret" in result: + del result["client_secret"] + + return result + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, + API_VERSION, + None, + "authproviders", + module.params["name"], + ) + + try: + providers = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json( + changed=False, + objects=[remove_item(utils.convert_v1_to_v2_response(p)) for p in providers], + ) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py b/ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py new file mode 100644 index 000000000..417218ec7 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/bonsai_asset.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: bonsai_asset +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Add Sensu assets from Bonsai +description: + - Create or update a Sensu Go asset whose definition is available in the + Bonsai, the Sensu asset index. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/assets/) + and U(https://bonsai.sensu.io/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +options: + version: + description: + - Version number of the asset to install. + type: str + required: true + rename: + description: + - The name that will be used when adding the asset to Sensu. + - If not present, value of the I(name) parameter will be used. + type: str + on_remote: + description: + - If set to C(true), module will download asset defnition on remote host. + - If not set or set to C(false), ansible downloads asset definition + on control node. + type: bool + version_added: 1.13.0 +notes: + - I(labels) and I(annotations) values are merged with the values obtained + from Bonsai. Values passed-in as parameters take precedence over the + values obtained from Bonsai. + - To delete an asset, use regular M(sensu.sensu_go.asset) module. +seealso: + - module: sensu.sensu_go.asset + - module: sensu.sensu_go.asset_info +""" + +EXAMPLES = """ +- name: Make sure specific version of asset is installed + sensu.sensu_go.bonsai_asset: + name: sensu/monitoring-plugins + version: 2.2.0-1 + +- name: Remove previously added asset + sensu.sensu_go.asset: + name: sensu/monitoring-plugins + state: absent + +- name: Store Bonsai asset under a different name + sensu.sensu_go.bonsai_asset: + name: sensu/monitoring-plugins + version: 2.2.0-1 + rename: sensu-monitoring-2.2.0-1 + +- name: Display asset info + sensu.sensu_go.asset_info: + name: sensu-monitoring-2.2.0-1 # value from rename field +""" + +RETURN = """ +object: + description: Object representing Sensu asset. + returned: success + type: dict + sample: + metadata: + name: check_script + namespace: default + builds: + - sha512: 4f926bf4328f...2c58ad9ab40c9e2edc31b288d066b195b21b + url: http://example.com/asset.tar.gz +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import bonsai, errors + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + name=dict( + type="str", + required=True, + ), + version=dict( + type="str", + required=True, + ), + ), + ) + + try: + asset = bonsai.get_asset_parameters( + module.params["name"], module.params["version"], + ) + module.exit_json(changed=False, asset=asset) + except errors.Error as e: + module.fail_json(changed=False, msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/check.py b/ansible_collections/sensu/sensu_go/plugins/modules/check.py new file mode 100644 index 000000000..47f446936 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/check.py @@ -0,0 +1,449 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: check +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu checks +description: + - Create, update or delete Sensu Go check. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/checks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations + - sensu.sensu_go.secrets +seealso: + - module: sensu.sensu_go.check_info +options: + command: + description: + - Check command to run. + - Required if I(state) is C(present). + type: str + subscriptions: + description: + - List of subscriptions which receive check requests. + - Required if I(state) is C(present). + type: list + elements: str + handlers: + description: + - List of handlers which receive check results. + type: list + elements: str + interval: + description: + - Check request interval. + - Cannot be used when I(cron) option is used. + type: int + cron: + description: + - Schedule check requests using crontab syntax. + - Cannot be used when I(interval) option is used. + type: str + publish: + description: + - Enables or disables scheduled publication of check requests. + type: bool + timeout: + description: + - Check execution timeout. + type: int + ttl: + description: + - Amount of time after which a check result is considered stale. + type: int + stdin: + description: + - Enables writing of serialized JSON data to the check command's stdin. + - Only usable with checks written specifically for Sensu Go. + type: bool + low_flap_threshold: + description: + - Low flap threshold. + type: int + high_flap_threshold: + description: + - High flap threshold. + type: int + runtime_assets: + description: + - List of runtime assets required to run the check. + type: list + elements: str + check_hooks: + description: + - A mapping of response codes to hooks which will be run by the agent + when that code is returned. + - Note that the structure of this parameter is a bit different from the + one described at + U(https://docs.sensu.io/sensu-go/latest/reference/checks/#check-hooks-attribute). + - See check hooks example below for more information on exact mapping + structure. + type: dict + proxy_entity_name: + description: + - Entity name to associate this check with instead of the agent it ran on. + type: str + proxy_requests: + description: + - Allows you to assign the check to run for multiple entities according + to their entity_attributes. + type: dict + suboptions: + entity_attributes: + description: + - List of attribute checks for determining which proxy entities this check should be scheduled against. + type: list + elements: str + splay: + description: + - Enables or disables splaying of check request scheduling. + type: bool + splay_coverage: + description: + - Percentage of the C(interval) over which to splay checks. + type: int + output_metric_format: + description: + - Enable parsing of metrics in the specified format from this check's + output. + type: str + choices: + - graphite_plaintext + - influxdb_line + - nagios_perfdata + - opentsdb_line + output_metric_handlers: + description: + - List of handlers which receive check results. I'm not sure why this exists. + type: list + elements: str + round_robin: + description: + - An array of environment variables to use with command execution. + type: bool + env_vars: + description: + - A mapping of environment variable names and values to use with command execution. + type: dict +''' + +EXAMPLES = ''' +- name: Check executing command every 30 seconds + sensu.sensu_go.check: + name: check + command: check-cpu.sh -w 75 -c 90 + subscriptions: + - checks + interval: 30 + publish: yes + +- name: Check executing command with cron scheduler + sensu.sensu_go.check: + name: check + command: check-cpu.sh -w 75 -c 90 + subscriptions: + - systems + handlers: + - slack + cron: "* * * * *" + publish: yes + +- name: Ad-hoc scheduling + sensu.sensu_go.check: + name: check + command: check-cpu.sh -w 75 -c 90 + subscriptions: + - systems + handlers: + - slack + interval: 60 + publish: no + +- name: Report events under proxy entity name instead of agent entity + sensu.sensu_go.check: + name: check + command: http_check.sh https://sensu.io + subscriptions: + - proxy + handlers: + - slack + interval: 60 + proxy_entity_name: sensu-site + round_robin: yes + publish: yes + +- name: Event that triggers hooks + sensu.sensu_go.check: + name: check + command: http_check.sh https://sensu.io + subscriptions: [ proxy ] + # The upstream JSON payload for the hooks below would look like this: + # + # "check_hooks": [ + # {"0": ["passing-hook", "always-run-this-hook"]}, + # {"critical": ["failing-hook", "always-run-this-hook"]} + # ] + # + # Ansible task simplifies this structure into a simple mapping: + check_hooks: + "0": + - passing-hook + - always-run-this-hook + critical: + - failing-hook + - always-run-this-hook + +- name: Remove check + sensu.sensu_go.check: + name: my-check + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu check. + returned: success + type: dict + sample: + metadata: + name: check_minimum + namespace: default + command: collect.sh + handlers: + - slack + interval: 10 + publish: true + subscriptions: + - system +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def validate_module_params(module): + params = module.params + proxy_requests = params['proxy_requests'] + + if (proxy_requests and proxy_requests.get('splay', False) and + proxy_requests.get('splay_coverage') is None): + module.fail_json(msg='splay is true but all of the following are missing: splay_coverage') + + if params['state'] == 'present' and not (params['interval'] or params['cron']): + module.fail_json(msg='one of the following is required: interval, cron') + + +def do_sets_differ(current, desired, key): + return set(current.get(key) or []) != set(desired.get(key) or []) + + +def do_proxy_requests_differ(current, desired): + if 'proxy_requests' not in desired: + return False + + current = current.get('proxy_requests') or {} + desired = desired['proxy_requests'] + + return ( + ( + 'entity_attributes' in desired and + do_sets_differ(current, desired, 'entity_attributes') + ) or + utils.do_differ(current, desired, 'entity_attributes') + ) + + +def do_check_hooks_differ(current, desired): + if 'check_hooks' not in desired: + return False + + current = utils.single_item_dicts_to_dict(current.get('check_hooks') or []) + current = dict((k, set(v)) for k, v in current.items()) + + desired = utils.single_item_dicts_to_dict(desired['check_hooks']) + desired = dict((k, set(v)) for k, v in desired.items()) + + return current != desired + + +def do_differ(current, desired): + return ( + utils.do_differ( + current, desired, 'proxy_requests', 'subscriptions', 'handlers', + 'runtime_assets', 'check_hooks', 'output_metric_handlers', + 'env_vars', 'secrets', + ) or + utils.do_secrets_differ(current, desired) or + do_proxy_requests_differ(current, desired) or + do_sets_differ(current, desired, 'subscriptions') or + do_sets_differ(current, desired, 'handlers') or + do_sets_differ(current, desired, 'runtime_assets') or + do_check_hooks_differ(current, desired) or + do_sets_differ(current, desired, 'output_metric_handlers') or + do_sets_differ(current, desired, 'env_vars') + ) + + +def build_api_payload(params): + payload = arguments.get_mutation_payload( + params, + 'command', + 'cron', + 'handlers', + 'high_flap_threshold', + 'interval', + 'low_flap_threshold', + 'output_metric_format', + 'output_metric_handlers', + 'proxy_entity_name', + 'publish', + 'round_robin', + 'runtime_assets', + 'secrets', + 'stdin', + 'subscriptions', + 'timeout', + 'ttl' + ) + + if params['proxy_requests']: + payload['proxy_requests'] = arguments.get_spec_payload( + params['proxy_requests'], + 'entity_attributes', 'splay', 'splay_coverage', + ) + + if params['check_hooks']: + payload['check_hooks'] = utils.dict_to_single_item_dicts(params['check_hooks']) + + if params['env_vars']: + payload['env_vars'] = utils.dict_to_key_value_strings(params['env_vars']) + + return payload + + +def main(): + required_if = [ + ('state', 'present', ['subscriptions', 'command']) + ] + mutually_exclusive = [('interval', 'cron')] + + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + "secrets", + ), + command=dict(), + subscriptions=dict( + type='list', elements='str', + ), + handlers=dict( + type='list', elements='str', + ), + interval=dict( + type='int' + ), + cron=dict(), + publish=dict( + type='bool' + ), + timeout=dict( + type='int' + ), + ttl=dict( + type='int' + ), + stdin=dict( + type='bool' + ), + env_vars=dict( + type='dict' + ), + low_flap_threshold=dict( + type='int' + ), + high_flap_threshold=dict( + type='int' + ), + runtime_assets=dict( + type='list', elements='str', + ), + check_hooks=dict( + type='dict' + ), + proxy_entity_name=dict(), + proxy_requests=dict( + type='dict', + options=dict( + entity_attributes=dict( + type='list', elements='str', + ), + splay=dict( + type='bool' + ), + splay_coverage=dict( + type='int' + ) + ) + ), + output_metric_format=dict( + choices=['nagios_perfdata', 'graphite_plaintext', 'influxdb_line', 'opentsdb_line'] + ), + output_metric_handlers=dict( + type='list', elements='str', + ), + round_robin=dict( + type='bool' + ) + ) + ) + validate_module_params(module) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'checks', module.params['name'], + ) + payload = build_api_payload(module.params) + + try: + changed, check = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=check) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/check_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/check_info.py new file mode 100644 index 000000000..3bb207c91 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/check_info.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: check_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu checks +description: + - Retrieve information about Sensu Go checks. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/checks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.check +''' + +EXAMPLES = ''' +- name: List all Sensu checks + sensu.sensu_go.check_info: + register: result + +- name: Obtain a specific check + sensu.sensu_go.check_info: + name: my-check + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu checks. + returned: success + type: list + elements: dict + sample: + - metadata: + name: check_minimum + namespace: default + command: collect.sh + handlers: + - slack + interval: 10 + publish: true + subscriptions: + - system +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "checks", module.params["name"], + ) + + try: + checks = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=checks) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster.py new file mode 100644 index 000000000..a75fbbe27 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster.py @@ -0,0 +1,116 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: cluster +author: + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Go clusters +description: + - Create, update or delete Sensu cluster. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/cluster-sensu/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.cluster_info +options: + api_urls: + description: + - List of API urls that compose a single cluster. + - Required if I(state) is C(present). + type: list + elements: str +""" + +EXAMPLES = """ +- name: Create a small cluster + sensu.sensu_go.cluster: + name: small-cluster + api_urls: https://sensu.alpha.example.com:8080 + +- name: Create a larger cluster + sensu.sensu_go.cluster: + name: large-cluster + api_urls: + - https://sensu.alpha.example.com:8080 + - https://sensu.beta.example.com:8080 + - https://sensu.gamma.example.com:8080 + +- name: Delete a cluster + sensu.sensu_go.cluster: + name: small-cluster + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu cluster. + returned: success + type: dict + sample: + metadata: + name: alpha-cluster + api_urls: + - "http://10.10.0.1:8080" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + required_if = [ + ("state", "present", ["api_urls"]), + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + api_urls=dict(type="list", elements="str"), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "clusters", module.params["name"], + ) + + payload = dict( + type="Cluster", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload(module.params, "api_urls"), + ) + try: + changed, cluster = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=cluster) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py new file mode 100644 index 000000000..0fb64c270 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_info.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: cluster_info +author: + - Tadej Borovsak (@tadeboro) +short_description: List available Sensu Go clusters +description: + - Retrieve information about Sensu Go clusters. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/cluster-sensu/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.cluster +""" + +EXAMPLES = """ +- name: List all Sensu Go clusters + sensu.sensu_go.etcd_replicator_info: + register: result + +- name: Retrieve the selected Sensu Go cluster + sensu.sensu_go.etcd_replicator_info: + name: my-cluster + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.api_urls }}" +""" + +RETURN = """ +objects: + description: List of Sensu Go etcd clusters. + returned: success + type: list + elements: dict + sample: + - metadata: + name: alpha-cluster + api_urls: + - "http://10.10.0.1:8080" + - metadata: + name: beta-cluster + api_urls: + - "https://10.20.0.1:8080" + - "https://10.20.0.2:8080" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "clusters", module.params["name"], + ) + + try: + clusters = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in clusters + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py new file mode 100644 index 000000000..20aecb54f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu cluster roles +description: + - Create, update or delete Sensu role. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.cluster_role_info + - module: sensu.sensu_go.cluster_role_binding + - module: sensu.sensu_go.role + - module: sensu.sensu_go.role_binding +options: + rules: + description: + - Rules that the cluster role applies. + - Must be non-empty if I(state) is C(present). + type: list + elements: dict + suboptions: + verbs: + description: + - Permissions to be applied by the rule. + type: list + elements: str + required: yes + choices: [get, list, create, update, delete] + resources: + description: + - Types of resources the rule has permission to access. + type: list + elements: str + required: yes + resource_names: + description: + - Names of specific resources the rule has permission to access. + - Note that for the C(create) verb, this argument will not be + taken into account when enforcing RBAC, even if it is provided. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a cluster role + sensu.sensu_go.cluster_role: + name: readonly + rules: + - verbs: + - get + - list + resources: + - checks + - entities + +- name: Delete a cluster role + sensu.sensu_go.cluster_role: + name: readonly + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu cluster role. + returned: success + type: dict + sample: + metadata: + name: cluster-role + rules: + - resource_names: + - sample-name + resources: + - assets + - checks + verbs: + - get + - list +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + rules=dict( + type="list", + elements="dict", + options=dict( + verbs=dict( + required=True, + type="list", + elements="str", + choices=["get", "list", "create", "update", "delete"], + ), + resources=dict( + required=True, + type="list", + elements="str", + ), + resource_names=dict( + type="list", + elements="str", + ), + ) + ) + ) + ) + + msg = role_utils.validate_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterroles", module.params["name"], + ) + payload = arguments.get_mutation_payload( + module.params, "rules" + ) + + try: + changed, cluster_role = utils.sync( + module.params['state'], client, path, + payload, module.check_mode, role_utils.do_roles_differ + ) + module.exit_json(changed=changed, object=cluster_role) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py new file mode 100644 index 000000000..de60d0252 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role_binding +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu cluster role bindings +description: + - Create, update or delete Sensu cluster role binding. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +options: + cluster_role: + description: + - Name of the cluster role. + - Required if I(state) is C(present). + type: str + users: + description: + - List of users to bind to the cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a cluster role binding. + type: list + elements: str + groups: + description: + - List of groups to bind to the cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a cluster role binding. + type: list + elements: str +seealso: + - module: sensu.sensu_go.cluster_role_binding_info + - module: sensu.sensu_go.cluster_role + - module: sensu.sensu_go.role_binding +''' + +EXAMPLES = ''' +- name: Create a cluster role binding + sensu.sensu_go.cluster_role_binding: + name: all-cluster-admins + cluster_role: cluster-admin + groups: + - cluster-admins + users: + - alice + +- name: Delete a cluster role binding + sensu.sensu_go.cluster_role_binding: + name: all-cluster-admins + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu cluster role binding. + returned: success + type: dict + sample: + metadata: + name: cluster-admin + role_ref: + name: cluster-admin + type: ClusterRole + subjects: + - name: cluster-admins + type: Group +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def build_api_payload(params): + payload = arguments.get_mutation_payload(params) + payload["subjects"] = role_utils.build_subjects(params["groups"], params["users"]) + payload["role_ref"] = role_utils.type_name_dict("ClusterRole", params["cluster_role"]) + + return payload + + +def main(): + required_if = [ + ("state", "present", ["cluster_role"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + cluster_role=dict(), + users=dict( + type="list", elements="str", + ), + groups=dict( + type="list", elements="str", + ), + ) + ) + + msg = role_utils.validate_binding_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterrolebindings", module.params["name"], + ) + payload = build_api_payload(module.params) + + try: + changed, cluster_role_binding = utils.sync( + module.params["state"], client, path, payload, module.check_mode, role_utils.do_role_bindings_differ + ) + module.exit_json(changed=changed, object=cluster_role_binding) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py new file mode 100644 index 000000000..8d46fdc99 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_binding_info.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role_binding_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu cluster role bindings +description: + - Retrieve information about Sensu cluster role bindings. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.cluster_role_binding +''' + +EXAMPLES = ''' +- name: List all Sensu cluster role bindings + sensu.sensu_go.cluster_role_binding_info: + register: result + +- name: Retrieve a specific Sensu cluster role binding + sensu.sensu_go.cluster_role_binding_info: + name: my-binding + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu cluster role bindings. + returned: success + type: list + elements: dict + sample: + - metadata: + name: cluster-admin + role_ref: + name: cluster-admin + type: ClusterRole + subjects: + - name: cluster-admins + type: Group +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict() + ) + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterrolebindings", module.params["name"], + ) + + try: + cluster_role_bindings = utils.prepare_result_list( + utils.get(client, path) + ) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=cluster_role_bindings) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py new file mode 100644 index 000000000..e2374dff8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/cluster_role_info.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: cluster_role_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu cluster roles +description: + - Retrieve information about Sensu roles. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.cluster_role +''' + +EXAMPLES = ''' +- name: List all Sensu cluster roles + sensu.sensu_go.cluster_role_info: + register: result + +- name: Retrieve Sensu cluster role by name + sensu.sensu_go.cluster_role_info: + name: my-custer-role + register: result +''' + +RETURN = ''' +roles: + description: List of Sensu cluster roles. + returned: success + type: list + elements: dict + sample: + - metadata: + name: cluster-role + rules: + - resource_names: + - sample-name + resources: + - assets + - checks + verbs: + - get + - list +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict() + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + None, "clusterroles", module.params["name"], + ) + + try: + cluster_roles = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=cluster_roles) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/datastore.py b/ansible_collections/sensu/sensu_go/plugins/modules/datastore.py new file mode 100644 index 000000000..706f3d1d8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/datastore.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: datastore +author: + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu external datastore providers +description: + - Add or remove external datastore provider. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/datastore/). +version_added: 1.1.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.datastore_info +options: + dsn: + description: + - Attribute that specifies the data source names as a URL or + PostgreSQL connection string. See the PostgreSQL docs for more + information about connection strings. + type: str + pool_size: + description: + - The maximum number of connections to hold in the PostgreSQL connection + pool. + type: int +notes: + - Currently, only one external datastore can be active at a time. The module + will fail to perform its operation if this would break that invariant. +''' + +EXAMPLES = ''' +- name: Add external datastore + sensu.sensu_go.datastore: + name: my-postgres + dsn: postgresql://user:secret@host:port/dbname + +- name: Remove external datastore + sensu.sensu_go.datastore: + name: my-postgres + state: absent +''' + +RETURN = ''' +object: + description: Object representing external datastore provider. + returned: success + type: dict + sample: + metadata: + name: my-postgres + batch_buffer: 0 + batch_size: 1 + batch_workers: 0 + dsn: "postgresql://user:secret@host:port/dbname" + max_conn_lifetime: 5m + max_idle_conns: 2 + pool_size: 20 + strict: true + enable_round_robin: true +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "store/v1" + + +def _get(client, path): + return utils.convert_v1_to_v2_response(utils.get(client, path)) + + +def sync(state, client, list_path, resource_path, payload, check_mode): + datastore = _get(client, resource_path) + + # When we are deleting stores, we do not care if there is more than one + # datastore present. We just make sure the currently manipulated store is + # gone. This makes our module useful in "let us clean up the mess" + # scenarios. + if state == "absent" and datastore is None: + return False, None + + if state == "absent": + if not check_mode: + utils.delete(client, resource_path) + return True, None + + # If the store exists, update it and ignore the fact that there might be + # more than one present. + if datastore: + if utils.do_differ(datastore, payload["spec"]): + if check_mode: + return True, payload["spec"] + utils.put(client, resource_path, payload) + return True, _get(client, resource_path) + return False, datastore + + # When adding a new datastore, we first make sure there is no other + # datastore present because we do not want to be the ones who brought + # backends into an inconsistent state. + if utils.get(client, list_path): + raise errors.Error("Some other external datastore is already active.") + + if check_mode: + return True, payload["spec"] + utils.put(client, resource_path, payload) + return True, _get(client, resource_path) + + +def main(): + required_if = [ + ("state", "present", ["dsn"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + dsn=dict(), + pool_size=dict( + type="int", + ) + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + list_path = utils.build_url_path(API_GROUP, API_VERSION, None, "provider") + resource_path = utils.build_url_path( + API_GROUP, API_VERSION, None, "provider", module.params["name"], + ) + payload = dict( + type="PostgresConfig", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload(module.params, "dsn", "pool_size"), + ) + + try: + changed, datastore = sync( + module.params["state"], client, list_path, resource_path, payload, + module.check_mode, + ) + module.exit_json(changed=changed, object=datastore) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py new file mode 100644 index 000000000..b31c6481a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/datastore_info.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: datastore_info +author: + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: List external Sensu datastore providers +description: + - Retrieve information about external Sensu datastores. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/datastore/). +version_added: 1.1.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.datastore +""" + +EXAMPLES = """ +- name: List all external Sensu datastores + sensu.sensu_go.datastore_info: + register: result + +- name: Retrieve the selected external Sensu datastore + sensu.sensu_go.datastore_info: + name: my-datastore + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.dsn }}" +""" + +RETURN = """ +objects: + description: List of external Sensu datastore providers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: my-postgres + batch_buffer: 0 + batch_size: 1 + batch_workers: 0 + dsn: "postgresql://user:secret@host:port/dbname" + max_conn_lifetime: 5m + max_idle_conns: 2 + pool_size: 20 + strict: true + enable_round_robin: true +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "store/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "provider", module.params["name"], + ) + + try: + stores = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in stores + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/entity.py b/ansible_collections/sensu/sensu_go/plugins/modules/entity.py new file mode 100644 index 000000000..e8d1562d9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/entity.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: entity +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu entities +description: + - Create, update or delete Sensu entity. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/entities/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.entity_info +options: + entity_class: + description: + - Entity class. Standard classes are C(proxy) and C(agent), but you can + use whatever you want. + - Required if I(state) is C(present). + type: str + subscriptions: + description: + - List of subscriptions for the entity. + type: list + elements: str + system: + description: + - System information about the entity, such as operating system and platform. See + U(https://docs.sensu.io/sensu-go/5.13/reference/entities/#system-attributes) for more information. + type: dict + last_seen: + description: + - Timestamp the entity was last seen, in seconds since the Unix epoch. + type: int + deregister: + description: + - If the entity should be removed when it stops sending keepalive messages. + type: bool + deregistration_handler: + description: + - The name of the handler to be called when an entity is deregistered. + type: str + redact: + description: + - List of items to redact from log messages. If a value is provided, + it overwrites the default list of items to be redacted. + type: list + elements: str + user: + description: + - Sensu RBAC username used by the entity. Agent entities require get, + list, create, update, and delete permissions for events across all namespaces. + type: str +''' + +EXAMPLES = ''' +- name: Create an entity + sensu.sensu_go.entity: + auth: + url: http://localhost:8080 + name: entity + entity_class: proxy + subscriptions: + - web + - prod + system: + hostname: playbook-entity + os: linux + platform: ubutntu + network: + interfaces: + - name: lo + addresses: + - 127.0.0.1/8 + - ::1/128 + - name: eth0 + mac: 52:54:00:20:1b:3c + addresses: + - 93.184.216.34/24 + last_seen: 1522798317 + deregister: yes + deregistration_handler: email-handler + redact: + - password + - pass + - api_key + user: agent + +- name: Delete an entity + sensu.sensu_go.entity: + name: entity + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu entity. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: webserver01 + namespace: default + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1542667231 + redact: + - password + - private_key + - secret + subscriptions: + - entity:webserver01 + system: + arch: amd64 + libc_type: glibc + vm_system: kvm + vm_role: host + cloud_provider: null + network: + interfaces: + - addresses: + - 127.0.0.1/8 + - ::1/128 + name: lo + - addresses: + - 172.28.128.3/24 + - fe80::a00:27ff:febc:be60/64 + mac: 08:00:27:bc:be:60 + name: enp0s8 + os: linux + platform: centos + platform_family: rhel + platform_version: 7.4.1708 + sensu_agent_version: 1.0.0 + user: agent +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def do_differ(current, desired): + system = desired.get('system') + if system and utils.do_differ(current.get('system'), system): + return True + + subs = desired.get('subscriptions') + if subs is not None and set(subs) != set(current.get('subscriptions', [])): + return True + + return utils.do_differ(current, desired, 'system', 'subscriptions') + + +def main(): + required_if = [ + ('state', 'present', ['entity_class']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + entity_class=dict(), + subscriptions=dict( + type='list', elements='str', + ), + system=dict( + type='dict' + ), + last_seen=dict( + type='int' + ), + deregister=dict( + type='bool' + ), + deregistration_handler=dict(), + redact=dict( + type='list', elements='str', + ), + user=dict() + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'entities', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'entity_class', 'subscriptions', 'system', 'last_seen', 'deregister', + 'redact', 'user' + ) + if module.params['deregistration_handler']: + payload['deregistration'] = dict(handler=module.params['deregistration_handler']) + + # As per conversation with @echlebek, the only two supported entity + # classes are agent and proxy. All other classes can lead to undefined + # behavior and should not be used. + eclass = payload.get('entity_class') + if eclass and eclass not in ('agent', 'proxy'): + deprecation_msg = ( + 'The `entity_class` parameter should be set to either `agent` or ' + '`proxy`. All other values can result in undefined behavior of ' + 'the Sensu Go backend.' + ) + utils.deprecate(module, deprecation_msg, '2.0.0') + + # Agent entities always have entity:{entity_name} subscription enabled + # even if we pass an empty subscriptions. In order to prevent falsely + # reporting changed: true, we always add this subscription to the agent + # entities. + if eclass == 'agent': + entity_sub = 'entity:' + module.params['name'] + subs = payload.get('subscriptions', []) + if entity_sub not in subs: + # Copy subs in order to avoid mutating module params + payload['subscriptions'] = subs + [entity_sub] + + try: + changed, entity = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=entity) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py new file mode 100644 index 000000000..511dd2372 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/entity_info.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: entity_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu entities +description: + - Retrieve information about Sensu entities. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/entities/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.entity +''' + +EXAMPLES = ''' +- name: List all Sensu entities + sensu.sensu_go.entity_info: + register: result + +- name: Retrieve a specific Sensu entity + sensu.sensu_go.entity_info: + name: my-entity + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu entities. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: webserver01 + namespace: default + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1542667231 + redact: + - password + - private_key + - secret + subscriptions: + - entity:webserver01 + system: + arch: amd64 + libc_type: glibc + vm_system: kvm + vm_role: host + cloud_provider: null + network: + interfaces: + - addresses: + - 127.0.0.1/8 + - ::1/128 + name: lo + - addresses: + - 172.28.128.3/24 + - fe80::a00:27ff:febc:be60/64 + mac: 08:00:27:bc:be:60 + name: enp0s8 + os: linux + platform: centos + platform_family: rhel + platform_version: 7.4.1708 + sensu_agent_version: 1.0.0 + user: agent +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "entities", module.params["name"], + ) + + try: + entities = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=entities) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py new file mode 100644 index 000000000..143aca8f0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: etcd_replicator +author: + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Go etcd replicators +description: + - Create, update or delete Sensu etcd replicator. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/etcdreplicators/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.etcd_replicator_info +options: + ca_cert: + description: + - Path to an the PEM-format CA certificate to use for TLS client authentication. + - Required if I(insecure) is C(false). + type: str + cert: + description: + - Path to the PEM-format certificate to use for TLS client authentication. This + certificate is required for secure client communication. + - Required if I(insecure) is C(false). + type: str + key: + description: + - Path to the PEM-format key file associated with the cert to use for TLS client + authentication. This key and its corresponding certificate are required for + secure client communication. + - Required if I(insecure) is C(false). + type: str + insecure: + description: + - Disable transport security. + - Only set to C(true) in sandbox and experimental environments. + type: bool + default: false + url: + description: + - Destination cluster URLs. + - Required if I(state) is C(present). + type: list + elements: str + api_version: + description: + - Sensu API version of the resource to replicate. + type: str + resource: + description: + - Name of the resource to replicate. + - List of all resources is available at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/rbac/#resources). + - Required if I(state) is C(present). + type: str + namespace: + description: + - Namespace to constrain replication to. + - If you do not include namespace, all namespaces for a given resource are + replicated. + type: str + replication_interval: + description: + - Interval at which the resource will be replicated. In seconds. + type: int +""" + +EXAMPLES = """ +- name: Create a minimal replicator + sensu.sensu_go.etcd_replicator: + name: cluster_role_replicator + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + key: /etc/sensu/certs/key.pem + url: https://sensu.alpha.example.com:2379 + resource: ClusterRole + +- name: Create an insecure minimal replicator + sensu.sensu_go.etcd_replicator: + name: role_replicator + insecure: true + url: + - https://sensu.beta.example.com:2379 + - https://sensu.gamma.example.com:2379 + resource: Role + +- name: Create a replicator with all parameters set + sensu.sensu_go.etcd_replicator: + name: role_binding_replicator + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + key: /etc/sensu/certs/key.pem + insecure: false + url: https://127.0.0.1:2379 + api_version: core/v2 + resource: RoleBinding + namespace: default + replication_interval_seconds: 30 + +- name: Delete a replicator + sensu.sensu_go.etcd_replicator: + name: my_replicator + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu etcd replicator. + returned: success + type: dict + sample: + metadata: + created_by: admin + name: cluster-role-replicator + api_version: core/v2 + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + insecure: false + key: /etc/sensu/certs/key.pem + namespace: "" + replication_interval_seconds: 30 + resource: ClusterRole + url: https://sensu.alpha.example.com:2379 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + required_if = [ + ("state", "present", ["url", "resource"]), + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state"), + ca_cert=dict(type="str"), + cert=dict(type="str"), + key=dict(type="str", no_log=False), + insecure=dict(type="bool", default=False), + url=dict(type="list", elements="str"), + api_version=dict(type="str"), + resource=dict(type="str"), + namespace=dict(type="str"), + replication_interval=dict(type="int"), + ), + ) + + # This complex condition cannot be expressed using built-in checks. + if module.params["state"] == "present" and module.params["insecure"] is False: + missing = [] + for key in ("ca_cert", "cert", "key"): + if not module.params[key]: + missing.append(key) + if missing: + msg = "insecure is False but all of the following are missing: {0}".format( + ", ".join(missing) + ) + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "etcd-replicators", module.params["name"], + ) + + spec = arguments.get_spec_payload( + module.params, "ca_cert", "cert", "key", "insecure", "api_version", + "resource", "namespace", + ) + # We renamed the replication interval a bit. + if module.params["replication_interval"] is not None: + spec["replication_interval_seconds"] = module.params["replication_interval"] + # We accept a list of urls that we need to convert here + if module.params["url"]: + spec["url"] = ",".join(module.params["url"]) + + payload = dict( + type="EtcdReplicator", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=spec, + ) + try: + changed, replicator = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=replicator) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py new file mode 100644 index 000000000..cf58d7c8b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/etcd_replicator_info.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: etcd_replicator_info +author: + - Tadej Borovsak (@tadeboro) +short_description: List available Sensu Go etcd replicators +description: + - Retrieve information about Sensu Go etcd replicators. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/deploy-sensu/etcdreplicators/). +version_added: 1.9.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.etcd_replicator +""" + +EXAMPLES = """ +- name: List all Sensu Go etcd replicators + sensu.sensu_go.etcd_replicator_info: + register: result + +- name: Retrieve the selected Sensu Go etcd replicator + sensu.sensu_go.etcd_replicator_info: + name: role_replicator + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.resource }}" +""" + +RETURN = """ +objects: + description: List of Sensu Go etcd replicators. + returned: success + type: list + elements: dict + sample: + - metadata: + created_by: admin + name: cluster-role-replicator + api_version: core/v2 + ca_cert: /etc/sensu/certs/ca.pem + cert: /etc/sensu/certs/cert.pem + insecure: false + key: /etc/sensu/certs/key.pem + namespace: "" + replication_interval_seconds: 30 + resource: ClusterRole + url: https://sensu.alpha.example.com:2379 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "federation/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "etcd-replicators", module.params["name"], + ) + + try: + replicators = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in replicators + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/event.py b/ansible_collections/sensu/sensu_go/plugins/modules/event.py new file mode 100644 index 000000000..ae2ae333d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/event.py @@ -0,0 +1,335 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: event +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu events +description: + - Send a synthetic event to Sensu. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/events/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.event_info +notes: + - Metric events bypass the store and are sent off to the event pipeline and corresponding event + handlers. Read more about this at + U(https://docs.sensu.io/sensu-go/latest/reference/events/#metric-only-events). +options: + timestamp: + description: + - UNIX time at which the event occurred. + type: int + entity: + description: + - Name of the entity associated with this event. It must exist before event creation. + type: str + required: true + check: + description: + - Name of the check associated with this event. It must exist before event creation. + type: str + required: true + check_attributes: + type: dict + description: + - Additional check parameters. Find out more at + U(https://docs.sensu.io/sensu-go/latest/reference/events/#check-attributes). + suboptions: + duration: + description: + - Command execution time in seconds. + type: float + executed: + description: + - Time that the check request was executed. + type: int + history: + description: + - Check status history for the last 21 check executions. + type: list + elements: dict + issued: + description: + - Time that the check request was issued in seconds since the Unix epoch. + type: int + last_ok: + description: + - The last time that the check returned an OK status (0) in seconds since the Unix epoch. + type: int + output: + description: + - The output from the execution of the check command. + type: str + state: + description: + - The state of the check. + choices: [ "passing", "failing", "flapping" ] + type: str + status: + description: + - Exit status code produced by the check. + choices: [ "ok", "warning", "critical", "unknown" ] + type: str + total_state_change: + description: + - The total state change percentage for the check's history. + type: int + metric_attributes: + type: dict + description: + - Metric attributes. Find out more at + U(https://docs.sensu.io/sensu-go/latest/reference/events/#metric-attributes). + suboptions: + handlers: + description: + - An array of Sensu handlers to use for events created by the check. + Each array item must be a string. + type: list + elements: str + points: + description: + - Metric data points including a name, timestamp, value, and tags. + type: list + elements: dict +''' + +EXAMPLES = ''' +- name: Create an event + sensu.sensu_go.event: + auth: + url: http://localhost:8080 + entity: awesome_entity + check: awesome_check + check_attributes: + duration: 1.945 + executed: 1522100915 + history: + - executed: 1552505193 + status: 1 + issued: 1552506034 + last_ok: 1552506033 + output: '10' + state: 'passing' + status: 'ok' + total_state_change: 0 + metric_attributes: + handlers: + - handler1 + - handler2 + points: + - name: "sensu-go-sandbox.curl_timings.time_total" + tags: + - name: "response_time_in_ms" + value: 101 + timestamp: 1552506033 + value: 0.005 + - name: "sensu-go-sandbox.curl_timings.time_namelookup" + tags: + - name: "namelookup_time_in_ms" + value: 57 + timestamp: 1552506033 + value: 0.004 +''' + +RETURN = ''' +object: + description: Object representing Sensu event (deprecated). + returned: success + type: dict + sample: + metadata: + namespace: default + check: + check_hooks: null + command: check-cpu.sh -w 75 -c 90 + duration: 1.07055808 + env_vars: null + executed: 1552594757 + handlers: [] + high_flap_threshold: 0 + history: + - executed: 1552594757 + status: 0 + interval: 60 + metadata: + name: check-cpu + namespace: default + occurrences: 1 + occurrences_watermark: 1 + output: CPU OK - Usage:3.96 + subscriptions: + - linux + timeout: 0 + total_state_change: 0 + ttl: 0 + entity: + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1552594641 + metadata: + name: sensu-centos + namespace: default + timestamp: 1552594758 + id: 3a5948f3-6ffd-4ea2-a41e-334f4a72ca2f + sequence: 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +STATUS_MAP = { + 'ok': 0, + 'warning': 1, + 'critical': 2, + 'unknown': 3, +} + + +def get_check(client, namespace, check): + check_path = utils.build_core_v2_path(namespace, 'checks', check) + resp = client.get(check_path) + if resp.status != 200: + raise errors.SyncError("Check with name '{0}' does not exist on remote.".format(check)) + return resp.json + + +def get_entity(client, namespace, entity): + entity_path = utils.build_core_v2_path(namespace, 'entities', entity) + resp = client.get(entity_path) + if resp.status != 200: + raise errors.SyncError("Entity with name '{0}' does not exist on remote.".format(entity)) + return resp.json + + +def _update_payload_with_metric_attributes(payload, metric_attributes): + if not metric_attributes: + return + + payload['metrics'] = arguments.get_spec_payload(metric_attributes, *metric_attributes.keys()) + + +def _update_payload_with_check_attributes(payload, check_attributes): + if not check_attributes: + return + + if check_attributes['status']: + check_attributes['status'] = STATUS_MAP[check_attributes['status']] + + filtered_attributes = arguments.get_spec_payload(check_attributes, *check_attributes.keys()) + payload['check'].update(filtered_attributes) + + +def _build_api_payload(client, params): + payload = arguments.get_spec_payload(params, 'timestamp') + payload['metadata'] = dict( + namespace=params['namespace'] + ) + payload['entity'] = get_entity(client, params['namespace'], params['entity']) + payload['check'] = get_check(client, params['namespace'], params['check']) + + _update_payload_with_check_attributes(payload, params['check_attributes']) + _update_payload_with_metric_attributes(payload, params['metric_attributes']) + return payload + + +def send_event(client, path, payload, check_mode): + if not check_mode: + utils.put(client, path, payload) + return True, payload + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + timestamp=dict(type='int'), + entity=dict(required=True), + check=dict(required=True), + check_attributes=dict( + type='dict', + options=dict( + duration=dict( + type='float' + ), + executed=dict( + type='int' + ), + history=dict( + type='list', elements='dict', + ), + issued=dict( + type='int' + ), + last_ok=dict( + type='int' + ), + output=dict(), + state=dict( + choices=['passing', 'failing', 'flapping'] + ), + status=dict( + choices=['ok', 'warning', 'critical', 'unknown'] + ), + total_state_change=dict( + type='int' + ) + ) + ), + metric_attributes=dict( + type='dict', + options=dict( + handlers=dict( + type='list', + elements='str' + ), + points=dict( + type='list', + elements='dict' + ) + ) + ) + ) + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'events', module.params['entity'], + module.params['check'], + ) + + try: + payload = _build_api_payload(client, module.params) + changed, event = send_event(client, path, payload, module.check_mode) + module.exit_json(changed=changed, object=event) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/event_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/event_info.py new file mode 100644 index 000000000..90a4d84ff --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/event_info.py @@ -0,0 +1,142 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: event_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu events +description: + - Retrieve recent events that Sensu processed. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/events/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.event +options: + check: + description: + - Limit results to a specific check. + - I(entity) must also be specified if this parameter is used. + type: str + entity: + description: + - Limit results to a specific entity. + type: str +''' + +EXAMPLES = ''' +- name: List Sensu events + sensu.sensu_go.event_info: + register: result + +- name: List Sensu events for api.example.com + sensu.sensu_go.event_info: + entity: api.example.com + register: result + +- name: Filter events by check and entity + sensu.sensu_go.event_info: + entity: api.example.com + check: check-cpu + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu events. + returned: success + type: list + elements: dict + sample: + - metadata: + namespace: default + check: + check_hooks: null + command: check-cpu.sh -w 75 -c 90 + duration: 1.07055808 + env_vars: null + executed: 1552594757 + handlers: [] + high_flap_threshold: 0 + history: + - executed: 1552594757 + status: 0 + interval: 60 + metadata: + name: check-cpu + namespace: default + occurrences: 1 + occurrences_watermark: 1 + output: CPU OK - Usage:3.96 + subscriptions: + - linux + timeout: 0 + total_state_change: 0 + ttl: 0 + entity: + deregister: false + deregistration: {} + entity_class: agent + last_seen: 1552594641 + metadata: + name: sensu-centos + namespace: default + timestamp: 1552594758 + id: 3a5948f3-6ffd-4ea2-a41e-334f4a72ca2f + sequence: 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_by = {'check': ['entity']} + module = AnsibleModule( + supports_check_mode=True, + required_by=required_by, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + check=dict(), + entity=dict(), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'events', module.params['entity'], + module.params['check'], + ) + + try: + events = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=events) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/filter.py b/ansible_collections/sensu/sensu_go/plugins/modules/filter.py new file mode 100644 index 000000000..e8023b436 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/filter.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: filter +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu filters +description: + - Create, update or delete Sensu filter. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/filters/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.filter_info +options: + action: + description: + - Action to take with the event if the filter expressions match. + - Required if I(state) is C(present). + type: str + choices: [ 'allow', 'deny' ] + expressions: + description: + - Filter expressions to be compared with event data. + - Required if I(state) is C(present). + type: list + elements: str + runtime_assets: + description: + - Assets to be applied to the filter's execution context. + JavaScript files in the lib directory of the asset will be evaluated. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a filter + sensu.sensu_go.filter: + name: filter + action: deny + expressions: + - event.check.interval == 10 + - event.check.occurrences == 1 + runtime_assets: awesomeness + +- name: Create a production filter + sensu.sensu_go.filter: + name: filter + action: allow + expressions: + - event.entity.labels['environment'] == 'production' + +- name: Create a filter with JS expression + sensu.sensu_go.filter: + name: filter + action: deny + expressions: + - "_.reduce(event.check.history, function(memo, h) { return (memo || h.status != 0); })" + runtime_assets: + - underscore + +- name: Handling repeated events + sensu.sensu_go.filter: + name: filter_interval_60_hourly + action: allow + expressions: + - event.check.interval == 60 + - event.check.occurrences == 1 || event.check.occurrences % 60 == 0 + +- name: Delete a filter + sensu.sensu_go.filter: + name: filter_interval_60_hourly + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu filter. + returned: success + type: dict + sample: + metadata: + name: filter_minimum + namespace: default + action: allow + expressions: + - event.check.occurrences == 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['action', 'expressions']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + action=dict(choices=['allow', 'deny']), + expressions=dict( + type='list', elements='str', + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'filters', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'action', 'expressions', 'runtime_assets' + ) + try: + changed, sensu_filter = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=sensu_filter) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py new file mode 100644 index 000000000..bdad1fa56 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/filter_info.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: filter_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu info +description: + - Retrieve information about Sensu filters. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/filters/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.filter +''' + +EXAMPLES = ''' +- name: List all Sensu filters + sensu.sensu_go.filter_info: + register: result + +- name: Retrieve a specific Sensu filter + sensu.sensu_go.filter_info: + name: my-filter + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu filters. + returned: success + type: list + elements: dict + sample: + - metadata: + name: filter_minimum + namespace: default + action: allow + expressions: + - event.check.occurrences == 1 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "filters", module.params["name"], + ) + + try: + sensu_filters = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=sensu_filters) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py new file mode 100644 index 000000000..600de0a1d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/handler_info.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: handler_info +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu handlers +description: + - Retrieve information about Sensu handlers. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.socket_handler + - module: sensu.sensu_go.pipe_handler + - module: sensu.sensu_go.handler_set +''' + +EXAMPLES = ''' +- name: List all Sensu handlers + sensu.sensu_go.handler_info: + register: result + +- name: Retrieve info for a specific Sensu handler + sensu.sensu_go.handler_info: + name: my-handler + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu handlers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: tcp_udp_handler_minimum + namespace: default + socket: + host: 10.0.1.99 + port: 4444 + type: tcp +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "handlers", module.params["name"], + ) + + try: + handlers = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=handlers) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py b/ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py new file mode 100644 index 000000000..aedc9856c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/handler_set.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: handler_set +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu handler set +description: + - Create, update or delete Sensu handler set. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/#handler-sets). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.socket_handler + - module: sensu.sensu_go.pipe_handler + - module: sensu.sensu_go.handler_info +options: + handlers: + description: + - List of Sensu event handlers (names) to use for events using the handler set. + - Required if I(state) is C(present). + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a handler set + sensu.sensu_go.handler_set: + name: notify_all_the_things + handlers: + - slack + - tcp_handler + - udp_handler + +- name: Delete a handler set + sensu.sensu_go.handler_set: + name: notify_all_the_things + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu handler set. + returned: success + type: dict + sample: + metadata: + name: tcp_udp_handler_minimum + namespace: default + handlers: + - slack + type: set +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['handlers']) + ] + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + handlers=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'handlers', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'handlers' + ) + payload['type'] = 'set' + + try: + changed, handler = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=handler) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/hook.py b/ansible_collections/sensu/sensu_go/plugins/modules/hook.py new file mode 100644 index 000000000..b78f90da5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/hook.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: hook +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu hooks +description: + - Create, update or delete Sensu hook. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/hooks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.hook_info +options: + command: + description: + - Command to run when the hook is triggered. + - Required if I(state) is C(present). + type: str + timeout: + description: + - The hook execution duration timeout in seconds (hard stop). + - Required if I(state) is C(present). + type: int + stdin: + description: + - Controls whether Sensu writes serialized JSON data to the process's stdin. + type: bool + runtime_assets: + description: + - List of runtime assets required to run the check. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Rudimentary auto-remediation hook + sensu.sensu_go.hook: + auth: + url: http://localhost:8080 + name: restart_nginx + command: sudo systemctl start nginx + timeout: 60 + stdin: false + +- name: Capture the process tree + sensu.sensu_go.hook: + auth: + url: http://localhost:8080 + name: process_tree + command: ps aux + timeout: 60 + stdin: false + +- name: Delete a hook + sensu.sensu_go.hook: + name: process_tree + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu hook. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: restart_nginx + namespace: default + command: sudo systemctl start nginx + stdin: false + timeout: 60 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['command', 'timeout']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + command=dict(), + timeout=dict( + type='int', + ), + stdin=dict( + type='bool' + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'hooks', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'command', 'timeout', 'stdin', 'runtime_assets' + ) + try: + changed, hook = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=hook) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py new file mode 100644 index 000000000..f576a5e4f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/hook_info.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: hook_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu hooks +description: + - Retrieve information about Sensu hooks. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/hooks/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.hook +''' + +EXAMPLES = ''' +- name: List all Sensu hooks + sensu.sensu_go.hook_info: + register: result + +- name: Fetch a specific Sensu hook + sensu.sensu_go.hook_info: + name: awesome-hook + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu hooks. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: restart_nginx + namespace: default + command: sudo systemctl start nginx + stdin: false + timeout: 60 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "hooks", module.params["name"], + ) + + try: + hooks = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=hooks) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py b/ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py new file mode 100644 index 000000000..e0ee4da04 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/ldap_auth_provider.py @@ -0,0 +1,378 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: ldap_auth_provider +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: Manage Sensu LDAP authentication provider + +description: + - Create, update or delete a Sensu Go LDAP authentication provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/ldap-auth/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state + +options: + servers: + description: + - An array of LDAP servers for your directory. + - Required if I(state) is C(present). + type: list + elements: dict + suboptions: + host: + description: + - LDAP server IP address. + required: true + type: str + port: + description: + - LDAP server port. + type: int + insecure: + description: + - Skips SSL certificate verification when set to true. + type: bool + default: false + security: + description: + - Encryption type to be used for the connection to the LDAP server. + type: str + choices: [ insecure, tls, starttls ] + default: tls + trusted_ca_file: + description: + - Path to an alternative CA bundle file. + type: str + client_cert_file: + description: + - Path to the certificate that should be sent to the server if requested. + type: str + client_key_file: + description: + - Path to the key file associated with the client_cert_file. + - Required if I(client_cert_file) is present. + type: str + binding: + description: + - The LDAP account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit the user_dn or password + attributes to query the directory without credentials. + type: dict + suboptions: + user_dn: + description: + - The LDAP account that performs user and group lookups. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + password: + description: + - Password for the user_dn account. + - If your sever supports anonymous binding, you can omit this attribute. + type: str + required: true + group_search: + description: + - Search configuration for groups. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: member + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: cn + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: groupOfNames + user_search: + description: + - Search configuration for users. + type: dict + suboptions: + base_dn: + description: + - Which part of the directory tree to search. + required: true + type: str + attribute: + description: + - Used for comparing result entries. + type: str + default: uid + name_attribute: + description: + - Represents the attribute to use as the entry name. + type: str + default: cn + object_class: + description: + - Identifies the class of objects returned in the search result. + type: str + default: person + groups_prefix: + description: + - The prefix added to all LDAP groups. + type: str + username_prefix: + description: + - The prefix added to all LDAP usernames. + type: str + +seealso: + - module: sensu.sensu_go.auth_provider_info + - module: sensu.sensu_go.ad_auth_provider + - module: sensu.sensu_go.oidc_auth_provider +""" + +EXAMPLES = """ +- name: Create a LDAP auth provider + sensu.sensu_go.ldap_auth_provider: + name: openldap + servers: + - host: 127.0.0.1 + group_search: + base_dn: dc=acme,dc=org + user_search: + base_dn: dc=acme,dc=org + +- name: Delete a LDAP auth provider + sensu.sensu_go.ldap_auth_provider: + name: openldap + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu LDAP authentication provider. + returned: success + type: dict + sample: + metadata: + name: 'openldap' + servers: + host: '127.0.0.1' + port: '636' + insecure: 'False' + security: 'tls' + trusted_ca_file: '/path/to/trusted-certificate-authorities.pem' + client_cert_file: '/path/to/ssl/cert.pem' + client_key_file: '/path/to/ssl/key.pem' + binding: + user_dn: 'cn=binder,dc=acme,dc=org' + group_search: + base_dn: 'dc=acme,dc=org' + attribute: 'member' + name_attribute': 'cn' + object_class: 'groupOfNames' + user_search: + base_dn: 'dc=acme,dc=org' + attribute: 'uid' + name_attribute: 'cn' + object_class: 'person' + groups_prefix: 'ldap' + username_prefix: 'ldap' +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + if result: + for server in result["servers"]: + if server["binding"] and "password" in server["binding"]: + del server["binding"]["password"] + + return result + + +def _filter(payload): + # Remove keys with None values from dict + return dict((k, v) for k, v in payload.items() if v is not None) + + +def do_differ(current, desired): + if utils.do_differ_v1(current, desired, "servers"): + return True + + if len(current["spec"]["servers"]) != len(desired["spec"]["servers"]): + return True + + for c, d in zip(current["spec"]["servers"], desired["spec"]["servers"]): + if utils.do_differ(c, _filter(d)): + return True + + return False + + +def main(): + required_if = [("state", "present", ["servers"])] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", + "name", + "state", + ), + servers=dict( + type="list", + elements="dict", + options=dict( + host=dict( + type="str", + required=True, + ), + port=dict( + type="int", + ), + insecure=dict( + type="bool", + default=False, + ), + security=dict( + type="str", + choices=["insecure", "tls", "starttls"], + default="tls", + ), + trusted_ca_file=dict( + type="str", + ), + client_cert_file=dict( + type="str", + ), + client_key_file=dict( + type="str", + ), + binding=dict( + type="dict", + options=dict( + user_dn=dict( + type="str", + required=True, + ), + password=dict( + type="str", + no_log=True, + required=True, + ), + ), + ), + group_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="member", + ), + name_attribute=dict( + type="str", + default="cn", + ), + object_class=dict(type="str", default="groupOfNames"), + ), + ), + user_search=dict( + type="dict", + options=dict( + base_dn=dict( + type="str", + required=True, + ), + attribute=dict( + type="str", + default="uid", + ), + name_attribute=dict( + type="str", + default="cn", + ), + object_class=dict( + type="str", + default="person", + ), + ), + ), + ), + ), + groups_prefix=dict( + type="str", + ), + username_prefix=dict( + type="str", + ), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "authproviders", module.params["name"] + ) + + payload = dict( + type="ldap", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload( + module.params, "servers", "groups_prefix", "username_prefix" + ), + ) + + try: + changed, ldap_provider = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=remove_item(ldap_provider)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/mutator.py b/ansible_collections/sensu/sensu_go/plugins/modules/mutator.py new file mode 100644 index 000000000..0baf8ca8c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/mutator.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: mutator +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu mutators +description: + - Create, update or delete Sensu mutator. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/mutators/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations + - sensu.sensu_go.secrets +seealso: + - module: sensu.sensu_go.mutator_info +options: + command: + description: + - The mutator command to be executed by the Sensu backend. + - Required if I(state) is C(present). + type: str + timeout: + description: + - The mutator execution duration timeout in seconds (hard stop). + type: int + env_vars: + description: + - A mapping of environment variable names and values to use with command execution. + type: dict + runtime_assets: + description: + - List of runtime assets, required to run the mutator I(command). + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a mutator + sensu.sensu_go.mutator: + name: mutator + command: sensu-influxdb-mutator + timeout: 30 + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + runtime_assets: + - sensu-influxdb-mutator + +- name: Delete a mutator + sensu.sensu_go.mutator: + name: mutator + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu mutator. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: example-mutator + namespace: default + command: example_mutator.go + env_vars: [] + runtime_assets: [] + timeout: 0 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def do_differ(current, desired): + return ( + utils.do_differ(current, desired, "secrets") or + utils.do_secrets_differ(current, desired) + ) + + +def main(): + required_if = [ + ('state', 'present', ['command']) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + "secrets", + ), + command=dict(), + timeout=dict( + type='int', + ), + env_vars=dict( + type='dict' + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'mutators', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'command', 'timeout', 'runtime_assets', 'secrets', + ) + if module.params['env_vars']: + payload['env_vars'] = utils.dict_to_key_value_strings(module.params['env_vars']) + try: + changed, mutator = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=mutator) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py new file mode 100644 index 000000000..1e2472056 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/mutator_info.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: mutator_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu mutators +description: + - Retrieve information about Sensu mutators. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/mutators/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.mutator +''' + +EXAMPLES = ''' +- name: List all Sensu mutators + sensu.sensu_go.mutator_info: + register: result + +- name: Retrieve a single Sensu mutator + sensu.sensu_go.mutator_info: + name: my-mutator + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu mutators. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: example-mutator + namespace: default + command: example_mutator.go + env_vars: [] + runtime_assets: [] + timeout: 0 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "mutators", module.params["name"], + ) + + try: + mutators = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=mutators) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/namespace.py b/ansible_collections/sensu/sensu_go/plugins/modules/namespace.py new file mode 100644 index 000000000..64d174cb6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/namespace.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: namespace +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu namespaces +description: + - Create, update or delete a Sensu namespace. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#namespaces). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.namespace_info +''' + +EXAMPLES = ''' +- name: Create a new namespace + sensu.sensu_go.namespace: + name: production + state: present + +- name: Delete a namespace + sensu.sensu_go.namespace: + name: staging + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu namespace. + returned: success + type: dict + sample: + name: default +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=arguments.get_spec("auth", "name", "state"), + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + None, 'namespaces', module.params['name'], + ) + payload = arguments.get_spec_payload( + module.params, 'name' + ) + try: + changed, namespace = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=namespace) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py new file mode 100644 index 000000000..d5d0a2690 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/namespace_info.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: namespace_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu namespaces +description: + - Retrieve information about Sensu namespaces. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#namespaces). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth +notes: + - Currently, it is not possible to retrieve information about a single + namespace because namespace is not much more than a name itself. +seealso: + - module: sensu.sensu_go.namespace +''' + +EXAMPLES = ''' +- name: List Sensu namespaces + sensu.sensu_go.namespace_info: + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu namespaces. + returned: success + type: list + elements: dict + sample: + - name: default +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + ), + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path(None, 'namespaces') + + try: + namespaces = utils.get(client, path) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=namespaces) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py b/ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py new file mode 100644 index 000000000..bcd5d4ed3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/oidc_auth_provider.py @@ -0,0 +1,248 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: oidc_auth_provider + +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) + +short_description: Manage Sensu OIDC authentication provider + +description: + - Create, update or delete a Sensu Go OIDC authentication provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/control-access/oidc-auth/). + +version_added: 1.10.0 + +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state + +options: + additional_scopes: + description: + - Scopes to include in the claims. + type: list + elements: str + default: openid + client_id: + description: + - The OIDC provider application Client ID. + - Required if I(state) is C(present). + type: str + client_secret: + description: + - The OIDC provider application Client Secret. + - Required if I(state) is C(present). + type: str + disable_offline_access: + description: + - If C(true), the OIDC provider cannot include the offline_access scope + in the authentication request. Otherwise, C(false). + type: bool + default: false + redirect_uri: + description: + - Redirect URL to provide to the OIDC provider. + type: str + server: + description: + - The location of the OIDC server you wish to authenticate against. + - Required if I(state) is C(present). + type: str + groups_claim: + description: + - The claim to use to form the associated RBAC groups. + type: str + groups_prefix: + description: + - The prefix added to all OIDC groups. + type: str + username_claim: + description: + - The claim to use to form the final RBAC user name. + - Required if I(state) is C(present). + type: str + username_prefix: + description: + - The prefix added to all OIDC usernames. + type: str + +seealso: + - module: sensu.sensu_go.auth_provider_info + - module: sensu.sensu_go.ldap_auth_provider + - module: sensu.sensu_go.ad_auth_provider + +notes: + - Supported only on Sensu Go versions >= 6. +""" + +EXAMPLES = """ +- name: Create a OIDC auth provider + sensu.sensu_go.oidc_auth_provider: + state: present + name: oidc_name + additional_scopes: + - groups + - email + client_id: a8e43af034e7f2608780 + client_secret: b63968394be6ed2edb61c93847ee792f31bf6216 + disable_offline_access: false + redirect_uri: http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback + server: https://oidc.example.com:9031 + groups_claim: groups + groups_prefix: 'oidc:' + username_claim: email + username_prefix: 'oidc:' + +- name: Delete a OIDC auth provider + sensu.sensu_go.oidc_auth_provider: + name: oidc_name + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu OIDC authentication provider. + returned: success + type: dict + sample: + metadata: + name: 'oidc_name' + created_by: 'admin' + additional_scopes: + - 'groups' + - 'email' + client_id: 'a8e43af034e7f2608780' + disable_offline_access: false + redirect_uri: 'http://sensu-backend.example.com:8080/api/enterprise/authentication/v2/oidc/callback' + server: 'https://oidc.example.com:9031' + groups_claim: 'groups' + groups_prefix: 'oidc:' + username_claim: 'email' + username_prefix: 'oidc:' +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "authentication/v2" + + +def remove_item(result): + if result and 'client_secret' in result: + del result['client_secret'] + + return result + + +def main(): + required_if = [ + ("state", "present", ["client_id", "client_secret", "server", "username_claim"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", + "name", + "state", + ), + additional_scopes=dict( + type="list", + elements="str", + default="openid", + ), + client_id=dict( + type="str", + ), + client_secret=dict( + type="str", + no_log=True, + ), + disable_offline_access=dict( + type="bool", + default=False, + ), + redirect_uri=dict( + type="str", + ), + server=dict( + type="str", + ), + groups_claim=dict( + type="str", + ), + groups_prefix=dict( + type="str", + ), + username_claim=dict( + type="str", + ), + username_prefix=dict( + type="str", + ), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "authproviders", module.params["name"] + ) + + payload = dict( + type="oidc", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=arguments.get_spec_payload( + module.params, + "additional_scopes", + "client_id", + "client_secret", + "disable_offline_access", + "redirect_uri", + "server", + "groups_claim", + "groups_prefix", + "username_claim", + "username_prefix", + ), + ) + + try: + changed, oidc_provider = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode + ) + module.exit_json( + changed=changed, object=remove_item(oidc_provider) + ) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py b/ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py new file mode 100644 index 000000000..99e985f7e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/pipe_handler.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: pipe_handler +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu pipe handler +description: + - Create, update or delete a Sensu pipe handler. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/#pipe-handlers). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations + - sensu.sensu_go.secrets +seealso: + - module: sensu.sensu_go.socket_handler + - module: sensu.sensu_go.handler_info + - module: sensu.sensu_go.handler_set +options: + command: + description: + - The handler command to be executed. The event data is passed to the + process through STDIN. + - Required if I(state) is C(present). + type: str + filters: + description: + - List of filters to use when determining whether to pass the check result to this handler. + type: list + elements: str + mutator: + description: + - Mutator to call for transforming the check result before passing it to this handler. + type: str + timeout: + description: + - Timeout for handler execution. + type: int + env_vars: + description: + - A mapping of environment variable names and values to use with command execution. + type: dict + runtime_assets: + description: + - List of runtime assets to required to run the handler C(command). + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Setup InfluxDB handler + sensu.sensu_go.pipe_handler: + name: influx-db + command: sensu-influxdb-handler -d sensu + env_vars: + INFLUXDB_ADDR: http://influxdb.default.svc.cluster.local:8086 + INFLUXDB_USER: sensu + INFLUXDB_PASS: password + runtime_assets: + - sensu-influxdb-handler + +- name: Delete handler + sensu.sensu_go.pipe_handler: + name: influx-db + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu pipe handler. + returned: success + type: dict + sample: + metadata: + name: pipe_handler_minimum + namespace: default + command: command-example + type: pipe +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def do_differ(current, desired): + return ( + utils.do_differ(current, desired, "secrets") or + utils.do_secrets_differ(current, desired) + ) + + +def main(): + required_if = [ + ('state', 'present', ['command']) + ] + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + "secrets", + ), + command=dict(), + filters=dict( + type='list', elements='str', + ), + mutator=dict(), + timeout=dict( + type='int' + ), + env_vars=dict( + type='dict' + ), + runtime_assets=dict( + type='list', elements='str', + ), + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'handlers', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'command', 'filters', 'mutator', 'timeout', + 'runtime_assets', 'secrets', + ) + payload['type'] = 'pipe' + if module.params['env_vars']: + payload['env_vars'] = utils.dict_to_key_value_strings(module.params['env_vars']) + + try: + changed, handler = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + do_differ, + ) + module.exit_json(changed=changed, object=handler) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role.py b/ansible_collections/sensu/sensu_go/plugins/modules/role.py new file mode 100644 index 000000000..54f58d131 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu roles +description: + - Create, update or delete Sensu role. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.role_info + - module: sensu.sensu_go.cluster_role + - module: sensu.sensu_go.role_binding +options: + rules: + description: + - Rules that the role applies. + - Must be non-empty if I(state) is C(present). + type: list + elements: dict + suboptions: + verbs: + description: + - Permissions to be applied by the rule. + type: list + elements: str + required: yes + choices: [get, list, create, update, delete] + resources: + description: + - Types of resources the rule has permission to access. + type: list + elements: str + required: yes + resource_names: + description: + - Names of specific resources the rule has permission to access. + - Note that for the C(create) verb, this argument will not be + taken into account when enforcing RBAC, even if it is provided. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a role + sensu.sensu_go.role: + name: readonly + rules: + - verbs: + - get + - list + resources: + - checks + - entities + +- name: Delete a role + sensu.sensu_go.role: + name: readonly + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu role. + returned: success + type: dict + sample: + metadata: + name: namespaced-resources-all-verbs + namespace: default + rules: + - resource_names: [] + resources: + - assets + - checks + verbs: + - create + - update + - delete +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state", "namespace"), + rules=dict( + type="list", + elements="dict", + options=dict( + verbs=dict( + required=True, + type="list", + elements="str", + choices=["get", "list", "create", "update", "delete"], + ), + resources=dict( + required=True, + type="list", + elements="str", + ), + resource_names=dict( + type="list", + elements="str", + ), + ) + ) + ) + ) + + msg = role_utils.validate_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "roles", module.params["name"], + ) + payload = arguments.get_mutation_payload( + module.params, "rules" + ) + + try: + changed, role = utils.sync( + module.params['state'], client, path, + payload, module.check_mode, role_utils.do_roles_differ + ) + module.exit_json(changed=changed, object=role) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py new file mode 100644 index 000000000..0bab8b1bc --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role_binding +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu role bindings +description: + - Create, update or delete Sensu role binding. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.role_binding_info + - module: sensu.sensu_go.role + - module: sensu.sensu_go.cluster_role + - module: sensu.sensu_go.cluster_role_binding +options: + role: + description: + - Name of the role. + - This parameter is mutually exclusive with I(cluster_role). + type: str + cluster_role: + description: + - Name of the cluster role. Note that the resulting role + binding grants the cluster role to the provided users and + groups in the context of I(auth.namespace) only. + - This parameter is mutually exclusive with I(role). + type: str + users: + description: + - List of users to bind to the role or cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a role binding. + type: list + elements: str + groups: + description: + - List of groups to bind to the role or cluster role. + - Note that at least one of I(users) and I(groups) must be + specified when creating a role binding. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a role binding + sensu.sensu_go.role_binding: + name: dev_and_testing + role: testers_permissive + groups: + - testers + - dev + - ops + users: + - alice + +- name: Create a role binding for admins + sensu.sensu_go.role_binding: + name: org-admins + cluster_role: admin + groups: + - team1-admins + - team2-admins + +- name: Delete a role binding + sensu.sensu_go.role_binding: + name: org-admins + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu role binding. + returned: success + type: dict + sample: + metadata: + name: event-reader-binding + namespace: default + role_ref: + name: event-reader + type: Role + subjects: + - name: bob + type: User +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils, role_utils + + +def infer_role(params): + if params["role"]: + return "Role", params["role"] + return "ClusterRole", params["cluster_role"] + + +def build_api_payload(params): + payload = arguments.get_mutation_payload(params) + payload["subjects"] = role_utils.build_subjects(params["groups"], params["users"]) + payload["role_ref"] = role_utils.type_name_dict(*infer_role(params)) + + return payload + + +def main(): + required_if = [ + ("state", "present", ["role", "cluster_role"], True) # True means any of role, cluster_role + ] + mutually_exclusive = [ + ["role", "cluster_role"] + ] + module = AnsibleModule( + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state", "namespace"), + role=dict(), + cluster_role=dict(), + users=dict( + type="list", elements="str", + ), + groups=dict( + type="list", elements="str", + ), + ) + ) + + msg = role_utils.validate_binding_module_params(module.params) + if msg: + module.fail_json(msg=msg) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "rolebindings", module.params["name"], + ) + payload = build_api_payload(module.params) + + try: + changed, role_binding = utils.sync( + module.params["state"], client, path, payload, module.check_mode, role_utils.do_role_bindings_differ + ) + module.exit_json(changed=changed, object=role_binding) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py new file mode 100644 index 000000000..ef7d230f8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role_binding_info.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role_binding_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu role bindings +description: + - Retrieve information about Sensu role bindings. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#role-bindings-and-cluster-role-bindings). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.role_binding +''' + +EXAMPLES = ''' +- name: List all Sensu role bindings + sensu.sensu_go.role_binding_info: + register: result + +- name: Retrieve a single Sensu role binding + sensu.sensu_go.role_binding_info: + name: my-role-binding + register: result +''' + +RETURN = ''' +role_bindings: + description: List of Sensu role bindings. + returned: success + type: list + elements: dict + sample: + - metadata: + name: event-reader-binding + namespace: default + role_ref: + name: event-reader + type: Role + subjects: + - name: bob + type: User +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict() + ) + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "rolebindings", module.params["name"], + ) + + try: + role_bindings = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=role_bindings) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/role_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/role_info.py new file mode 100644 index 000000000..cf2c7e8ff --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/role_info.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: role_info +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu roles +description: + - Retrieve information about Sensu roles. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#roles-and-cluster-roles). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.role +''' + +EXAMPLES = ''' +- name: List all Sensu roles + sensu.sensu_go.role_info: + register: result + +- name: Retrieve a specific Sensu role + sensu.sensu_go.role_info: + name: my-role + register: result +''' + +RETURN = ''' +roles: + description: List of Sensu roles. + returned: success + type: list + elements: dict + sample: + - metadata: + name: namespaced-resources-all-verbs + namespace: default + rules: + - resource_names: [] + resources: + - assets + - checks + verbs: + - create + - update + - delete +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict() + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "roles", module.params["name"], + ) + + try: + roles = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=roles) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secret.py b/ansible_collections/sensu/sensu_go/plugins/modules/secret.py new file mode 100644 index 000000000..6b0125578 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secret.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: secret +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Go secrets +description: + - Create, update or delete Sensu secret. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.secret_info + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secrets_provider_info +options: + provider: + description: + - Name of the secrets provider that backs the secret value. + - Required if I(state) is C(present). + type: str + id: + description: + - Secret's id in the provider store. + - Required if I(state) is C(present). + type: str +""" + +EXAMPLES = """ +- name: Create an environment varibale-backed secret + sensu.sensu_go.secret: + name: env_secret + provider: env + id: MY_ENV_VARIABLE + +- name: Create a HashiCorp Vault-backed secret + sensu.sensu_go.secret: + name: hashi_valut_secret + provider: vault + id: secret/store#name + +- name: Delete a secret + sensu.sensu_go.secret: + name: my_secret + state: absent +""" + +RETURN = """ +object: + description: Object representing Sensu secret. + returned: success + type: dict + sample: + metadata: + name: sensu-ansible + namespace: default + id: 'secret/database#password' + provider: vault +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + required_if = [ + ("state", "present", ["provider", "id"]) + ] + module = AnsibleModule( + required_if=required_if, + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name", "state", "namespace"), + provider=dict(type="str"), + id=dict(type="str"), + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, module.params["namespace"], "secrets", + module.params["name"], + ) + payload = dict( + type="Secret", + api_version=API_VERSION, + metadata=dict( + name=module.params["name"], + namespace=module.params["namespace"], + ), + spec=arguments.get_spec_payload(module.params, "provider", "id"), + ) + try: + changed, secret = utils.sync_v1( + module.params["state"], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=secret) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py new file mode 100644 index 000000000..a16ca82a3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secret_info.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: secret_info +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: List available Sensu Go secrets +description: + - Retrieve information about Sensu Go secrets. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secrets_provider_info +""" + +EXAMPLES = """ +- name: List all Sensu Go secrets + sensu.sensu_go.secret_info: + register: result + +- name: Retrieve the selected Sensu Go secret + sensu.sensu_go.secret_info: + name: my-secret + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.id }}" +""" + +RETURN = """ +objects: + description: List of Sensu Go secrets. + returned: success + type: list + elements: dict + sample: + - metadata: + name: sensu-ansible-token + namespace: default + id: ANSIBLE_TOKEN + provider: env + - metadata: + name: sensu-ansible + namespace: default + id: 'secret/database#password' + provider: vault +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "namespace"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, module.params["namespace"], "secrets", + module.params["name"], + ) + + try: + secrets = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(s) for s in secrets + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py new file mode 100644 index 000000000..258b6db09 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_env.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: secrets_provider_env +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu Env secrets provider +description: + - Create or delete a Sensu Go Env secrets provider. + - The module operates on a secrets provider named C(env). + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets-providers/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.state +seealso: + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secrets_provider_info + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secret_info +''' + +EXAMPLES = ''' +- name: Create the env secrets provider + sensu.sensu_go.secrets_provider_env: + +- name: Delete the env secrets provider + sensu.sensu_go.secrets_provider_env: + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu Env secrets provider. + returned: success + type: dict + sample: + - metadata: + name: env +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec( + "auth", "state", + ), + ) + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, 'providers', 'env' + ) + payload = dict( + type="Env", + api_version=API_VERSION, + metadata=dict(name='env'), + spec={}, + ) + + try: + changed, env_provider = utils.sync_v1( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=env_provider) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py new file mode 100644 index 000000000..b3aa1b0e3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_info.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = """ +module: secrets_provider_info +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu secrets providers +description: + - Retrieve information about Sensu Go secrets providers. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets-providers/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_vault + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secret_info +""" + +EXAMPLES = """ +- name: List all Sensu secrets providers + sensu.sensu_go.secrets_provider_info: + register: result + +- name: List the selected Sensu secrets provider + sensu.sensu_go.secrets_provider_info: + name: my_provider + register: result + +- name: Do something with result + ansible.builtin.debug: + msg: "{{ result.objects.0.metadata.name }}" +""" + +RETURN = """ +objects: + description: List of Sensu secrets providers. + returned: success + type: list + elements: dict + sample: + - metadata: + name: vault + client: + address: https://vaultserver.example.com:8200 + token: VAULT_TOKEN + version: v1 + tls: + ca_cert: "/etc/ssl/certs/vault_ca_cert.pem" + max_retries: 2 + timeout: 20s + rate_limiter: + limit: 10 + burst: 100 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, "providers", module.params["name"], + ) + + try: + providers = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + # We simulate the behavior of v2 API here and only return the spec. + module.exit_json(changed=False, objects=[ + utils.convert_v1_to_v2_response(p) for p in providers + ]) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py new file mode 100644 index 000000000..2df0a3108 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/secrets_provider_vault.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: secrets_provider_vault +author: + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Miha Dolinar (@mdolin) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu VaultProvider secrets providers +description: + - Create, update or delete a Sensu Go VaultProvider secrets provider. + - For more information, refer to the Sensu Go documentation at + U(https://docs.sensu.io/sensu-go/latest/operations/manage-secrets/secrets-providers/). +version_added: 1.6.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.state +options: + address: + description: + - Address of the Vault server. + - Required if I(state) is C(present). + type: str + token: + description: + - Authentication token to use with Vault. + - Required if I(state) is C(present). + type: str + version: + description: + - Version of the Vault key/value store. + - Please refer to U(https://www.vaultproject.io/docs/secrets/kv) for + additional information. + - Required if I(state) is C(present). + type: str + choices: [v1, v2] + timeout: + description: + - Timeout (in seconds) for connection to Vault server. + type: int + max_retries: + description: + - Maximum number of times to retry failed connections to Vault server. + type: int + rate_limit: + description: + - Maximum number of secrets requests for per second. + type: float + burst_limit: + description: + - Maximum allowed number of secrets requests in a rate interval. + type: int + tls: + description: + - TLS configuration for establishing connection with Vault server. + type: dict + suboptions: + ca_cert: + description: + - Path to the certificate file of the trusted certificate authority. + type: str + client_cert: + description: + - Path to the client certificate file. + type: str + client_key: + description: + - Path to the client key file. + type: str + cname: + description: + - Canonical name for the client. + type: str + +seealso: + - module: sensu.sensu_go.secrets_provider_env + - module: sensu.sensu_go.secrets_provider_info + - module: sensu.sensu_go.secret + - module: sensu.sensu_go.secret_info +''' + +EXAMPLES = ''' +- name: Create a vault secrets provider + sensu.sensu_go.secrets_provider_vault: + name: my-vault + address: https://my-vault.com + token: VAULT_TOKEN + version: v1 + +- name: Delete a vault secrets provider + sensu.sensu_go.secrets_provider_vault: + name: my-vault + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu vault secrets provider. + returned: success + type: dict + sample: + metadata: + name: vault + client: + address: https://vaultserver.example.com:8200 + token: VAULT_TOKEN + version: v1 + tls: + ca_cert: "/etc/ssl/certs/vault_ca_cert.pem" + max_retries: 2 + timeout: 20s + rate_limiter: + limit: 10 + burst: 100 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + +API_GROUP = "enterprise" +API_VERSION = "secrets/v1" + + +def do_differ(current, desired): + if utils.do_differ_v1(current, desired, "client"): + return True + + current_client = current["spec"]["client"] + desired_client = desired["spec"]["client"] + # Sensu Go API returns 'agent_address' field in the client spec, + # but this field is not meant to be set via the providers API. + if utils.do_differ(current_client, desired_client, "agent_address", "tls"): + return True + # Sensu Go API returns some extra fields in the tls spec. + # We ignore them, as they are not meant to be set via the + # providers API. + return utils.do_differ(current_client["tls"], desired_client.get("tls") or {}, + "insecure", "tls_server_name", "ca_path") + + +def _format_seconds(seconds): + # Sensu API returns the configured timeout as a string, for instance + # 30 -> '30s', 60-> '1m0s', 3600 -> '1h0m0s'. + h, r = divmod(seconds, 3600) + m, s = divmod(r, 60) + if h: + return "{0}h{1}m{2}s".format(h, m, s) + if m: + return "{0}m{1}s".format(m, s) + return "{0}s".format(seconds) + + +def build_vault_provider_spec(params): + if params["state"] == "absent": + return {} + + client = arguments.get_spec_payload( + params, "address", "token", "version", "max_retries", + ) + if params.get("tls"): + client["tls"] = arguments.get_spec_payload( + params["tls"], "ca_cert", "client_cert", "client_key", "cname", + ) + if params.get("timeout"): + client["timeout"] = _format_seconds(params["timeout"]) + + if params.get("rate_limit") or params.get("burst_limit"): + client["rate_limiter"] = arguments.get_renamed_spec_payload( + params, dict( + rate_limit="limit", + burst_limit="burst", + ) + ) + + return dict(client=client) + + +def main(): + required_if = [ + ("state", "present", ["address", "token", "version"]) + ] + + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", + ), + address=dict(), + token=dict(no_log=True), + version=dict( + choices=["v1", "v2"], + ), + timeout=dict( + type="int", + ), + max_retries=dict( + type="int", + ), + rate_limit=dict( + type="float", + ), + burst_limit=dict( + type="int", + ), + tls=dict( + type="dict", + options=dict( + ca_cert=dict(), + cname=dict(), + client_cert=dict(), + client_key=dict(no_log=False), + ) + ) + ) + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_url_path( + API_GROUP, API_VERSION, None, 'providers', module.params['name'] + ) + + payload = dict( + type="VaultProvider", + api_version=API_VERSION, + metadata=dict(name=module.params["name"]), + spec=build_vault_provider_spec(module.params) + ) + + try: + changed, vault_provider = utils.sync_v1( + module.params['state'], client, path, payload, module.check_mode, do_differ + ) + module.exit_json(changed=changed, object=vault_provider) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/silence.py b/ansible_collections/sensu/sensu_go/plugins/modules/silence.py new file mode 100644 index 000000000..4b2879117 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/silence.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: silence +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu silences +description: + - Create, update or delete Sensu silence. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/silencing/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.silence_info +options: + subscription: + description: + - The name of the subscription the entry should match. + - If left empty a silencing entry will contain an asterisk in the + subscription position. This indicates that any event with a matching + check name will be marked as silenced, regardless of the originating + entities subscriptions. + - Specific entity can also be targeted by taking advantage of per-entity + subscription (entity:). + - This parameter is required if the I(check) parameter is absent. + type: str + check: + description: + - The name of the check the entry should match. + - If left empty a silencing entry will contain an asterisk in the check + position. This indicates that any event where the originating entities + subscriptions match the subscription specified in the entry will be + marked as silenced, regardless of the check name. + - This parameter is required if the I(subscription) parameter is absent. + type: str + begin: + description: + - UNIX time at which silence entry goes into effect. + type: int + expire: + description: + - Number of seconds until the silence expires. + type: int + expire_on_resolve: + description: + - If the entry should be deleted when a check begins return OK status (resolves). + type: bool + reason: + description: + - Reason for silencing. + type: str +''' + +EXAMPLES = ''' +- name: Silence a specific check + sensu.sensu_go.silence: + subscription: proxy + check: check-disk + +- name: Silence specific check regardless of the originating entities subscription + sensu.sensu_go.silence: + check: check-cpu + +- name: Silence all checks on a specific entity + sensu.sensu_go.silence: + subscription: entity:important-entity + expire: 120 + reason: rebooting the world + +- name: Delete a silencing entry + sensu.sensu_go.silence: + subscription: entity:important-entity + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu silence. + returned: success + type: dict + sample: + metadata: + annotations: null + labels: null + name: entity:i-424242:* + namespace: default + begin: 1542671205 + check: null + creator: admin + expire: -1 + expire_on_resolve: false + reason: null + subscription: entity:i-424242 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_one_of = [ + ['subscription', 'check'] + ] + module = AnsibleModule( + supports_check_mode=True, + required_one_of=required_one_of, + argument_spec=dict( + arguments.get_spec( + 'auth', 'state', 'labels', 'annotations', 'namespace', + ), + subscription=dict(), + check=dict(), + begin=dict( + type='int', + ), + expire=dict( + type='int', + ), + expire_on_resolve=dict( + type='bool' + ), + reason=dict() + ), + ) + name = '{0}:{1}'.format(module.params['subscription'] or '*', module.params['check'] or '*') + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'silenced', name, + ) + # We add name parameter because it is actually required and must match the name that is + # autogenerated on the API + module.params['name'] = name + payload = arguments.get_mutation_payload( + module.params, 'subscription', 'check', 'begin', 'expire', 'expire_on_resolve', 'reason' + ) + try: + changed, silence = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=silence) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py new file mode 100644 index 000000000..68769902a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/silence_info.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: silence_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Manca Bizjak (@mancabizjak) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu silence entries +description: + - Retrieve information about Sensu silences. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/silencing/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.namespace +seealso: + - module: sensu.sensu_go.silence +options: + subscription: + description: + - The name of the subscription the entry should match. If left empty a silencing entry will + contain an asterisk in the subscription position. + type: str + check: + description: + - The name of the check the entry should match. If left empty a silencing entry will contain an + asterisk in the check position. + type: str +''' + +EXAMPLES = ''' +- name: List all Sensu silence entries + sensu.sensu_go.silence_info: + register: result + +- name: Fetch a specific silence with name proxy:awesome_check + sensu.sensu_go.silence_info: + subscription: proxy + check: awesome_check + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu silence entries. + returned: success + type: list + elements: dict + sample: + - metadata: + annotations: null + labels: null + name: entity:i-424242:* + namespace: default + begin: 1542671205 + check: null + creator: admin + expire: -1 + expire_on_resolve: false + reason: null + subscription: entity:i-424242 +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec('auth', 'namespace'), + subscription=dict(), + check=dict(), + ), + ) + + name = '{0}:{1}'.format(module.params['subscription'] or '*', module.params['check'] or '*') + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path( + module.params["namespace"], "silenced", None if name == "*:*" else name, + ) + + try: + silences = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=silences) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py b/ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py new file mode 100644 index 000000000..ac42d27c3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/socket_handler.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: socket_handler +author: + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu TCP/UDP handler +description: + - Create, update or delete Sensu socket handler. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/handlers/#tcp-udp-handlers). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name + - sensu.sensu_go.namespace + - sensu.sensu_go.state + - sensu.sensu_go.labels + - sensu.sensu_go.annotations +seealso: + - module: sensu.sensu_go.handler_info + - module: sensu.sensu_go.pipe_handler + - module: sensu.sensu_go.handler_set +options: + type: + description: + - The handler type. + - Required if I(state) is C(present). + choices: + - tcp + - udp + type: str + filters: + description: + - List of filters to use when determining whether to pass the check result to this handler. + type: list + elements: str + mutator: + description: + - Mutator to call for transforming the check result before passing it to this handler. + type: str + timeout: + description: + - Timeout for handler execution. + type: int + host: + description: + - The socket host address (IP or hostname) to connect to. + - Required if I(state) is C(present). + type: str + port: + description: + - The socket port to connect to. + - Required if I(state) is C(present). + type: int +''' + +EXAMPLES = ''' +- name: TCP handler + sensu.sensu_go.socket_handler: + name: tcp_handler + type: tcp + host: 10.0.1.99 + port: 4444 + +- name: UDP handler + sensu.sensu_go.socket_handler: + name: udp_handler + type: udp + host: 10.0.1.99 + port: 4444 + +- name: Delete a handler + sensu.sensu_go.socket_handler: + name: udp_handler + state: absent +''' + +RETURN = ''' +object: + description: Object representing Sensu socket handler. + returned: success + type: dict + sample: + - metadata: + name: udp_handler + namespace: default + socket: + host: 10.0.1.99 + port: 4444 + type: udp +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + required_if = [ + ('state', 'present', ['type', 'host', 'port']) + ] + module = AnsibleModule( + supports_check_mode=True, + required_if=required_if, + argument_spec=dict( + arguments.get_spec( + "auth", "name", "state", "labels", "annotations", "namespace", + ), + type=dict(choices=['tcp', 'udp']), + filters=dict( + type='list', elements='str', + ), + mutator=dict(), + timeout=dict( + type='int' + ), + host=dict(), + port=dict( + type='int' + ) + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path( + module.params['namespace'], 'handlers', module.params['name'], + ) + payload = arguments.get_mutation_payload( + module.params, 'type', 'filters', 'mutator', 'timeout' + ) + payload['socket'] = dict(host=module.params['host'], port=module.params['port']) + + try: + changed, handler = utils.sync( + module.params['state'], client, path, payload, module.check_mode, + ) + module.exit_json(changed=changed, object=handler) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/tessen.py b/ansible_collections/sensu/sensu_go/plugins/modules/tessen.py new file mode 100644 index 000000000..ba18f518f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/tessen.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: tessen +author: + - Paul Arthur (@flowerysong) + - Manca Bizjak (@mancabizjak) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu's Tessen configuration +description: + - Enable or disable Tessen service. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/tessen/). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth +options: + state: + description: + - Enable or disable sending anonymized data to Sensu Inc. + choices: [ enabled, disabled ] + type: str + required: True +''' + +EXAMPLES = ''' +- name: Disable Tessen + sensu.sensu_go.tessen: + state: disabled + register: result +''' + +RETURN = ''' +object: + description: Object representing Sensu tessen. + returned: success + type: dict + sample: + opt_out: false +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def get(client, path): + resp = client.get(path) + if resp.status != 200: + raise errors.SyncError( + "GET {0} failed with status {1}: {2}".format(path, resp.status, resp.data)) + return resp.json + + +def sync(client, path, payload, check_mode): + remote_object = get(client, path) + + if utils.do_differ(remote_object, payload): + if check_mode: + return True, payload + utils.put(client, path, payload) + return True, get(client, path) + + return False, remote_object + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec('auth'), + state=dict( + choices=['enabled', 'disabled'], + required=True, + ) + ) + ) + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path(None, 'tessen') + payload = dict( + opt_out=module.params['state'] == 'disabled' + ) + + try: + changed, tessen = sync(client, path, payload, module.check_mode) + module.exit_json(changed=changed, object=tessen) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/user.py b/ansible_collections/sensu/sensu_go/plugins/modules/user.py new file mode 100644 index 000000000..4ef155867 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/user.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: user +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Tadej Borovsak (@tadeboro) +short_description: Manage Sensu users +description: + - Create, update, activate or deactivate Sensu user. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#users). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.name +requirements: + - bcrypt (when managing Sensu Go 5.21.0 or newer) +seealso: + - module: sensu.sensu_go.user_info +options: + state: + description: + - Desired state of the user. + - Users cannot actually be deleted, only deactivated. + type: str + choices: [ enabled, disabled ] + default: enabled + password: + description: + - Password for the user. + - Required if user with a desired name does not exist yet on the backend + and I(password_hash) is not set. + - If both I(password) and I(password_hash) are set, I(password_hash) is + ignored and calculated from the I(password) if required. + type: str + password_hash: + description: + - Bcrypt password hash for the user. + - Use C(sensuctl user hash-password PASSWORD) to generate a hash. + - Required if user with a desired name does not exist yet on the backend + and I(password) is not set. + - If both I(password) and I(password_hash) are set, I(password_hash) is + ignored and calculated from the I(password) if required. + - Sensu Go < 5.21.0 does not support creating/updating users using + hashed passwords. Use I(password) parameter if you need to manage such + Sensu Go installations. + - At the moment, change detection does not work properly when using + password hashes because the Sensu Go backend does not expose enough + information via its API. + type: str + version_added: 1.8.0 + groups: + description: + - List of groups user belongs to. + type: list + elements: str +''' + +EXAMPLES = ''' +- name: Create a user + sensu.sensu_go.user: + auth: + url: http://localhost:8080 + name: awesome_username + password: hidden_password? + groups: + - dev + - prod + +- name: Use pre-hashed password + sensu.sensu_go.user: + auth: + url: http://localhost:8080 + name: awesome_username + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + +- name: Deactivate a user + sensu.sensu_go.user: + name: awesome_username + state: disabled +''' + +RETURN = ''' +object: + description: Object representing Sensu user. + returned: success + type: dict + sample: + disabled: false + groups: + - ops + - dev + password: USER_PASSWORD + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + username: alice +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib + +from ..module_utils import arguments, errors, utils + +try: + import bcrypt + HAS_BCRYPT = True + BCRYPT_IMPORT_ERROR = None +except ImportError: + HAS_BCRYPT = False + BCRYPT_IMPORT_ERROR = traceback.format_exc() + + +def _simulate_backend_response(payload): + # Backend does not return back any password-related information for now. + masked_keys = ('password', 'password_hash') + return dict( + (k, v) for k, v in payload.items() if k not in masked_keys + ) + + +def update_password(client, path, username, password, check_mode): + # Hit the auth testing API and try to validate the credentials. If the API + # says they are invalid, we need to update them. + if client.validate_auth_data(username, password): + return False + + if not check_mode: + if client.version < "5.21.0": + utils.put(client, path + '/password', dict( + username=username, password=password, + )) + else: + hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) + utils.put(client, path + '/reset_password', dict( + username=username, password_hash=hash.decode('ascii'), + )) + + return True + + +def update_password_hash(client, path, username, password_hash, check_mode): + # Some older Sensu Go versions do not have support for password hashes. + if client.version < "5.21.0": + raise errors.SensuError( + "Sensu Go < 5.21.0 does not support password hashes" + ) + + # Insert change detection here once we can receive password hash from the + # backend. Up until then, we always update passwords. + + if not check_mode: + utils.put(client, path + '/reset_password', dict( + username=username, password_hash=password_hash, + )) + + return True + + +def update_groups(client, path, old_groups, new_groups, check_mode): + to_delete = set(old_groups).difference(new_groups) + to_add = set(new_groups).difference(old_groups) + + if not check_mode: + # Next few lines are far from atomic, which means that we can leave a + # user in any of the intermediate states, but this is the best we can + # do given the API limitations. + for g in to_add: + utils.put(client, path + '/groups/' + g, None) + for g in to_delete: + utils.delete(client, path + '/groups/' + g) + + return len(to_delete) + len(to_add) > 0 + + +def update_state(client, path, old_disabled, new_disabled, check_mode): + changed = old_disabled != new_disabled + + if not check_mode and changed: + if new_disabled: # `state: disabled` input parameter + utils.delete(client, path) + else: # `state: enabled` input parameter + utils.put(client, path + '/reinstate', None) + + return changed + + +def sync(remote_object, client, path, payload, check_mode): + # Create new user (either enabled or disabled) + if remote_object is None: + if check_mode: + return True, _simulate_backend_response(payload) + utils.put(client, path, payload) + return True, utils.get(client, path) + + # Update existing user. We do this on a field-by-field basis because the + # upsteam API for updating users requires a password field to be set. Of + # course, we do not want to force users to specify an existing password + # just for the sake of updating the group membership, so this is why we + # use field-specific API endpoints to update the user data. + + changed = False + + # We only use password hash if we do not have a password. In practice, + # this means that users should not set both password and password_hash. We + # do not enforce this by making those two parameters mutually exclusive + # because in the future (2.0.0 version of collection), we intend to move + # password hashing into action plugin and supply both the password and its + # hash. Why? Because installing bcrypt on control node is way friendlier + # compared to installing bcrypt on every host that runs our user module. + # + # It is true that most of the time, control node == target node in our + # cases, but not always. + if 'password' in payload: + changed = update_password( + client, path, payload['username'], payload['password'], + check_mode, + ) or changed + elif 'password_hash' in payload: + changed = update_password_hash( + client, path, payload['username'], payload['password_hash'], + check_mode, + ) or changed + + if 'groups' in payload: + changed = update_groups( + client, path, remote_object.get('groups') or [], + payload['groups'], check_mode, + ) or changed + + if 'disabled' in payload: + changed = update_state( + client, path, remote_object['disabled'], payload['disabled'], + check_mode, + ) or changed + + if check_mode: + # Backend does not return back passwords, so we should follow the + # example set by the backend API. + return changed, dict( + remote_object, **_simulate_backend_response(payload) + ) + + return changed, utils.get(client, path) + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth", "name"), + state=dict( + default='enabled', + choices=['enabled', 'disabled'], + ), + password=dict( + no_log=True + ), + password_hash=dict( + no_log=False, # Showing hashes is perfectly OK + ), + groups=dict( + type='list', elements='str', + ) + ), + ) + + client = arguments.get_sensu_client(module.params['auth']) + path = utils.build_core_v2_path(None, 'users', module.params['name']) + + try: + if not HAS_BCRYPT and client.version >= "5.21.0": + module.fail_json( + msg=missing_required_lib('bcrypt'), + exception=BCRYPT_IMPORT_ERROR, + ) + except errors.SensuError as e: + module.fail_json(msg=str(e)) + + try: + remote_object = utils.get(client, path) + except errors.Error as e: + module.fail_json(msg=str(e)) + + if ( + remote_object is None + and module.params['password'] is None + and module.params['password_hash'] is None + ): + module.fail_json( + msg='Cannot create new user without a password or a hash' + ) + + payload = arguments.get_spec_payload( + module.params, 'password', 'password_hash', 'groups', + ) + payload['username'] = module.params['name'] + payload['disabled'] = module.params['state'] == 'disabled' + + try: + changed, user = sync( + remote_object, client, path, payload, module.check_mode + ) + module.exit_json(changed=changed, object=user) + except errors.Error as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/sensu/sensu_go/plugins/modules/user_info.py b/ansible_collections/sensu/sensu_go/plugins/modules/user_info.py new file mode 100644 index 000000000..ad9eda8fb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/plugins/modules/user_info.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Paul Arthur +# Copyright: (c) 2019, XLAB Steampunk +# +# 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": ["stableinterface"], + "supported_by": "certified", +} + +DOCUMENTATION = ''' +module: user_info +author: + - Paul Arthur (@flowerysong) + - Aljaz Kosir (@aljazkosir) + - Miha Plesko (@miha-plesko) + - Tadej Borovsak (@tadeboro) +short_description: List Sensu users +description: + - Retrieve information about Sensu users. + - For more information, refer to the Sensu documentation at + U(https://docs.sensu.io/sensu-go/latest/reference/rbac/#users). +version_added: 1.0.0 +extends_documentation_fragment: + - sensu.sensu_go.requirements + - sensu.sensu_go.auth + - sensu.sensu_go.info +seealso: + - module: sensu.sensu_go.user +''' + +EXAMPLES = ''' +- name: List Sensu users + sensu.sensu_go.user_info: + register: result + +- name: Retrieve a single Sensu user + sensu.sensu_go.user_info: + name: my-user + register: result +''' + +RETURN = ''' +objects: + description: List of Sensu users. + returned: success + type: list + elements: dict + sample: + - disabled: false + groups: + - ops + - dev + password: USER_PASSWORD + password_hash: $5f$14$.brXRviMZpbaleSq9kjoUuwm67V/s4IziOLGHjEqxJbzPsreQAyNm + username: alice +''' + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils import arguments, errors, utils + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + arguments.get_spec("auth"), + name=dict(), # Name is not required in info modules. + ), + ) + client = arguments.get_sensu_client(module.params["auth"]) + path = utils.build_core_v2_path(None, "users", module.params["name"]) + + try: + users = utils.prepare_result_list(utils.get(client, path)) + except errors.Error as e: + module.fail_json(msg=str(e)) + + module.exit_json(changed=False, objects=users) + + +if __name__ == '__main__': + main() -- cgit v1.2.3