From c64ed68e57b737c37d274fe63a3ec50ad60c665b Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 28 May 2025 11:58:34 +0200 Subject: [PATCH 01/16] Initial commit of modules donated by Atruvia AG. --- plugins/doc_fragments/common.py | 13 + plugins/lookup/bi_aggregation.py | 236 ++++++++++ plugins/lookup/bi_pack.py | 191 ++++++++ plugins/lookup/bi_rule.py | 234 ++++++++++ plugins/lookup/dcd.py | 253 +++++++++++ plugins/module_utils/differ.py | 101 +++++ plugins/module_utils/extended_api.py | 59 +++ plugins/modules/bi_aggregation.py | 528 ++++++++++++++++++++++ plugins/modules/bi_pack.py | 453 +++++++++++++++++++ plugins/modules/bi_rule.py | 545 +++++++++++++++++++++++ plugins/modules/dcd.py | 625 +++++++++++++++++++++++++++ 11 files changed, 3238 insertions(+) create mode 100644 plugins/lookup/bi_aggregation.py create mode 100644 plugins/lookup/bi_pack.py create mode 100644 plugins/lookup/bi_rule.py create mode 100644 plugins/lookup/dcd.py create mode 100644 plugins/module_utils/differ.py create mode 100644 plugins/module_utils/extended_api.py create mode 100644 plugins/modules/bi_aggregation.py create mode 100644 plugins/modules/bi_pack.py create mode 100644 plugins/modules/bi_rule.py create mode 100644 plugins/modules/dcd.py diff --git a/plugins/doc_fragments/common.py b/plugins/doc_fragments/common.py index d6d906c31..44c02a275 100644 --- a/plugins/doc_fragments/common.py +++ b/plugins/doc_fragments/common.py @@ -22,6 +22,19 @@ class ModuleDocFragment(object): description: The secret to authenticate your automation user. required: true type: str + auth_type: + description: Type of authentication to use. + required: false + type: str + choices: + - bearer + - basic + - cookie + default: bearer + auth_cookie: + description: Authentication cookie for the Checkmk session. + required: false + type: str validate_certs: description: Whether to validate the SSL certificate of the Checkmk server. default: true diff --git a/plugins/lookup/bi_aggregation.py b/plugins/lookup/bi_aggregation.py new file mode 100644 index 000000000..7ba0c82f0 --- /dev/null +++ b/plugins/lookup/bi_aggregation.py @@ -0,0 +1,236 @@ +# Copyright: (c) 2025, Lars Getwan +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: bi_aggregation + author: Lars Getwan (@lgetwan) + version_added: "6.1.0" + + short_description: Get BI aggregation attributes + + description: + - Returns the attributes of a BI aggregation. + + options: + + _terms: + description: BI aggregation ID + required: True + + server_url: + description: URL of the Checkmk server + required: True + vars: + - name: ansible_lookup_checkmk_server_url + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SERVER_URL + ini: + - section: checkmk_lookup + key: server_url + + site: + description: Site name. + required: True + vars: + - name: ansible_lookup_checkmk_site + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SITE + ini: + - section: checkmk_lookup + key: site + + auth_type: + description: Authentication type to use with the Checkmk API. + type: str + required: False + choices: ["bearer", "basic", "cookie"] + default: "bearer" + vars: + - name: ansible_lookup_checkmk_auth_type + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTH_TYPE + ini: + - section: checkmk_lookup + key: auth_type + + automation_user: + description: Automation user for the REST API access. + required: False + vars: + - name: ansible_lookup_checkmk_automation_user + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTOMATION_USER + ini: + - section: checkmk_lookup + key: automation_user + + automation_secret: + description: Automation secret for the REST API access. + required: False + vars: + - name: ansible_lookup_checkmk_automation_secret + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTOMATION_SECRET + ini: + - section: checkmk_lookup + key: automation_secret + + auth_cookie: + description: Authentication cookie for the REST API access. + required: False + vars: + - name: ansible_lookup_checkmk_auth_cookie + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTH_COOKIE + ini: + - section: checkmk_lookup + key: auth_cookie + + validate_certs: + description: Whether or not to validate TLS certificates. + type: boolean + required: False + default: True + vars: + - name: ansible_lookup_checkmk_validate_certs + env: + - name: ANSIBLE_LOOKUP_CHECKMK_VALIDATE_CERTS + ini: + - section: checkmk_lookup + key: validate_certs + + notes: + - Like all lookups, this runs on the Ansible controller and is unaffected by other keywords such as 'become'. + - The directory of the play is used as the current working directory. +""" + +# TODO Add examples here and check return + +RETURN = """ +_list: + description: + - A list of dicts of attributes of the BI aggregation(s), including rules and aggregations. + type: list + elements: dict +""" + +import base64 +import json + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible_collections.checkmk.general.plugins.module_utils.lookup_api import ( + CheckMKLookupAPI, +) + + +class ExtendedCheckmkAPI(CheckMKLookupAPI): + """ + Extends CheckMKLookupAPI to support 'basic' and 'cookie' authentication methods. + Ensures that Bearer authentication uses both 'automation_user' and 'automation_secret'. + """ + + def __init__( + self, + site_url, + auth_type="bearer", + automation_user=None, + automation_secret=None, + auth_cookie=None, + validate_certs=True, + ): + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + cookies = {} + + # Bearer Authentication: "Bearer USERNAME PASSWORD" + if auth_type == "bearer": + if not automation_user or not automation_secret: + raise ValueError( + "`automation_user` and `automation_secret` are required for bearer authentication." + ) + headers["Authorization"] = f"Bearer {automation_user} {automation_secret}" + + # Basic Authentication + elif auth_type == "basic": + if not automation_user or not automation_secret: + raise ValueError( + "`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + headers["Authorization"] = f"Basic {auth_b64}" + + # Cookie Authentication + elif auth_type == "cookie": + if not auth_cookie: + raise ValueError("`auth_cookie` is required for cookie authentication.") + cookies["auth_cmk"] = auth_cookie + + else: + raise ValueError(f"Unsupported `auth_type`: {auth_type}") + + super().__init__( + site_url=site_url, user="", secret="", validate_certs=validate_certs + ) + self.headers.update(headers) + self.cookies = cookies + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + self.set_options(var_options=variables, direct=kwargs) + aggregation_id = terms[0] + server_url = self.get_option("server_url") + site = self.get_option("site") + auth_type = self.get_option("auth_type") + automation_user = self.get_option("automation_user") + automation_secret = self.get_option("automation_secret") + auth_cookie = self.get_option("auth_cookie") + validate_certs = self.get_option("validate_certs") + + site_url = f"{server_url.rstrip('/')}/{site}" + + try: + api = ExtendedCheckmkAPI( + site_url=site_url, + auth_type=auth_type, + automation_user=automation_user, + automation_secret=automation_secret, + auth_cookie=auth_cookie, + validate_certs=validate_certs, + ) + except ValueError as e: + raise AnsibleError(str(e)) + + ret = [] + + try: + api_endpoint = f"/objects/bi_aggregation/{aggregation_id}" + response_content = api.get(api_endpoint) + response = json.loads(response_content) + + if "code" in response: + raise AnsibleError( + "Received error for %s - %s: %s" + % ( + response.get("url", ""), + response.get("code", ""), + response.get("msg", ""), + ) + ) + + ret.append(response) + + except Exception as e: + raise AnsibleError( + f"Error fetching BI aggregation '{aggregation_id}': {str(e)}" + ) + + return ret diff --git a/plugins/lookup/bi_pack.py b/plugins/lookup/bi_pack.py new file mode 100644 index 000000000..5d260f133 --- /dev/null +++ b/plugins/lookup/bi_pack.py @@ -0,0 +1,191 @@ +# Copyright: (c) 2025, Lars Getwan +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: bi_pack + author: Lars Getwan (@lgetwan) + version_added: "6.1.0" + + short_description: Get BI pack attributes + + description: + - Returns the attributes of a BI Pack in Checkmk, including its rules and aggregations. + + options: + + _terms: + description: BI Pack ID + required: True + + server_url: + description: URL of the Checkmk server + required: True + vars: + - name: ansible_lookup_checkmk_server_url + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SERVER_URL + ini: + - section: checkmk_lookup + key: server_url + + site: + description: Site name. + required: True + vars: + - name: ansible_lookup_checkmk_site + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SITE + ini: + - section: checkmk_lookup + key: site + + automation_user: + description: + - Automation user for the REST API access. + required: True + vars: + - name: ansible_lookup_checkmk_automation_user + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTOMATION_USER + ini: + - section: checkmk_lookup + key: automation_user + + automation_secret: + description: + - Automation secret for the REST API access. + required: True + vars: + - name: ansible_lookup_checkmk_automation_secret + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTOMATION_SECRET + ini: + - section: checkmk_lookup + key: automation_secret + + validate_certs: + description: + - Whether or not to validate TLS certificates. + type: boolean + required: False + default: True + vars: + - name: ansible_lookup_checkmk_validate_certs + env: + - name: ANSIBLE_LOOKUP_CHECKMK_VALIDATE_CERTS + ini: + - section: checkmk_lookup + key: validate_certs + + notes: + - Like all lookups, this runs on the Ansible controller and is unaffected by other keywords such as 'become'. + - The directory of the play is used as the current working directory. +""" + +EXAMPLES = """ +- name: Get the attributes of a BI Pack + ansible.builtin.debug: + msg: "Attributes of BI Pack: {{ attributes }}" + vars: + attributes: "{{ + lookup('checkmk.general.bi_pack', + 'example_pack', + server_url=my_server_url, + site=mysite, + automation_user=myuser, + automation_secret=mysecret, + validate_certs=False + ) + }}" + +- name: "Use variables outside the module call." + ansible.builtin.debug: + msg: "Attributes of BI Pack: {{ attributes }}" + vars: + ansible_lookup_checkmk_server_url: "http://myserver/" + ansible_lookup_checkmk_site: "mysite" + ansible_lookup_checkmk_automation_user: "myuser" + ansible_lookup_checkmk_automation_secret: "mysecret" + ansible_lookup_checkmk_validate_certs: false + attributes: "{{ lookup('checkmk.general.bi_pack', 'example_pack') }}" +""" + +RETURN = """ +_list: + description: + - A list of dicts of attributes of the BI Pack(s), including rules and aggregations. + type: list + elements: dict +""" + +import json + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible_collections.checkmk.general.plugins.module_utils.lookup_api import ( + CheckMKLookupAPI, +) + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + self.set_options(var_options=variables, direct=kwargs) + + try: + server_url = self.get_option("server_url") + site = self.get_option("site") + user = self.get_option("automation_user") + secret = self.get_option("automation_secret") + validate_certs = self.get_option("validate_certs") + except KeyError as e: + raise AnsibleError(f"Missing required configuration option: {str(e)}") + + site_url = f"{server_url.rstrip('/')}/{site}" + + api = CheckMKLookupAPI( + site_url=site_url, + user=user, + secret=secret, + validate_certs=validate_certs, + ) + + ret = [] + + for term in terms: + try: + # Fetch the BI Pack details + api_endpoint = f"/objects/bi_pack/{term}" + response = json.loads(api.get(api_endpoint)) + + if "code" in response: + raise AnsibleError( + "Received error for %s - %s: %s" + % ( + response.get("url", ""), + response.get("code", ""), + response.get("msg", ""), + ) + ) + + # Fetch the rules associated with the BI Pack + rules_endpoint = f"/objects/bi_pack/{term}/rules" + rules_response = json.loads(api.get(rules_endpoint)) + + # Fetch the aggregations associated with the BI Pack + aggregations_endpoint = f"/objects/bi_pack/{term}/aggregations" + aggregations_response = json.loads(api.get(aggregations_endpoint)) + + # Add rules and aggregations to the BI pack response + response["rules"] = rules_response.get("rules", []) + response["aggregations"] = aggregations_response.get("aggregations", []) + + ret.append(response) + + except Exception as e: + raise AnsibleError(f"Error fetching BI Pack '{term}': {str(e)}") + + return ret diff --git a/plugins/lookup/bi_rule.py b/plugins/lookup/bi_rule.py new file mode 100644 index 000000000..217bf8fb1 --- /dev/null +++ b/plugins/lookup/bi_rule.py @@ -0,0 +1,234 @@ +# Copyright: (c) 2025, Lars Getwan +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: bi_rule + author: Lars Getwan (@lgetwan) + version_added: "6.1.0" + + short_description: Retrieve details of a BI rule. + + description: + - This lookup returns the details of a specified BI rule. + + options: + + _terms: + description: The ID of the BI rule to retrieve. + required: True + + server_url: + description: URL of the Checkmk server + required: True + vars: + - name: ansible_lookup_checkmk_server_url + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SERVER_URL + ini: + - section: checkmk_lookup + key: server_url + + site: + description: Site name. + required: True + vars: + - name: ansible_lookup_checkmk_site + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SITE + ini: + - section: checkmk_lookup + key: site + + auth_type: + description: The authentication type to use ('bearer', 'basic', 'cookie'). + required: False + default: 'bearer' + + automation_user: + description: The automation user for authentication. + required: True + + automation_secret: + description: The automation secret or password for authentication. + required: True + + auth_cookie: + description: The authentication cookie value if using cookie-based authentication. + required: False + + validate_certs: + description: Whether to validate SSL certificates. + required: False + type: bool + default: True + + notes: + - Like all lookups, this runs on the Ansible controller and is unaffected by other keywords such as 'become'. + - The directory of the play is used as the current working directory. + - It is B(NOT) possible to assign other variables to the variables mentioned in the C(vars) section! + This is a limitation of Ansible itself. +""" + +EXAMPLES = """ +- name: Get a BI rule with a particular rule id + ansible.builtin.debug: + msg: "BI rule: {{ bi_rule_details }}" + vars: + bi_rule_details: "{{ + lookup('checkmk.general.bi_rule', + 'rule123', + server_url='http://myserver/', + site='mysite', + automation_user='myuser', + automation_secret='mysecret', + validate_certs=False + ) + }}" + +- name: "Use variables outside the module call." + ansible.builtin.debug: + msg: "BI rule: {{ bi_rule_details }}" + vars: + ansible_lookup_checkmk_server_url: "http://myserver/" + ansible_lookup_checkmk_site: "mysite" + ansible_lookup_checkmk_automation_user: "myuser" + ansible_lookup_checkmk_automation_secret: "mysecret" + ansible_lookup_checkmk_validate_certs: false + bi_rule_details: "{{ lookup('checkmk.general.bi_rule', 'rule123') }}" +""" + +RETURN = """ +_list: + description: + - A list of dicts containing the attributes of the BI rule, including its aggregations and related objects. + type: list + elements: dict +""" + +import base64 +import json + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible_collections.checkmk.general.plugins.module_utils.lookup_api import ( + CheckMKLookupAPI, +) + + +class ExtendedCheckmkAPI(CheckMKLookupAPI): + """ + Extends CheckMKLookupAPI to support 'basic' and 'cookie' authentication methods. + Ensures that Bearer authentication uses both 'automation_user' and 'automation_secret'. + """ + + def __init__( + self, + site_url, + auth_type="bearer", + automation_user=None, + automation_secret=None, + auth_cookie=None, + validate_certs=True, + ): + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + cookies = {} + + # Bearer Authentication: "Bearer USERNAME PASSWORD" + if auth_type == "bearer": + if not automation_user or not automation_secret: + raise ValueError( + "`automation_user` and `automation_secret` are required for bearer authentication." + ) + headers["Authorization"] = f"Bearer {automation_user} {automation_secret}" + + # Basic Authentication + elif auth_type == "basic": + if not automation_user or not automation_secret: + raise ValueError( + "`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + headers["Authorization"] = f"Basic {auth_b64}" + + # Cookie Authentication + elif auth_type == "cookie": + if not auth_cookie: + raise ValueError("`auth_cookie` is required for cookie authentication.") + cookies["auth_cmk"] = auth_cookie + + else: + raise ValueError(f"Unsupported `auth_type`: {auth_type}") + + super().__init__( + site_url=site_url, user="", secret="", validate_certs=validate_certs + ) + self.headers.update(headers) + self.cookies = cookies + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + self.set_options(var_options=variables, direct=kwargs) + rule_id = terms[0] + + try: + server_url = self.get_option("server_url") + site = self.get_option("site") + auth_type = self.get_option("auth_type") or "bearer" + automation_user = self.get_option("automation_user") + automation_secret = self.get_option("automation_secret") + auth_cookie = self.get_option("auth_cookie") + validate_certs = self.get_option("validate_certs") + except KeyError as e: + raise AnsibleError(f"Missing required configuration option: {str(e)}") + + site_url = f"{server_url.rstrip('/')}/{site}" + + # Optional: Debugging prints + # print(f"auth_type: {auth_type}") + # print(f"server_url: {server_url}") + # print(f"rule_id: {rule_id}") + + try: + api = ExtendedCheckmkAPI( + site_url=site_url, + auth_type=auth_type, + automation_user=automation_user, + automation_secret=automation_secret, + auth_cookie=auth_cookie, + validate_certs=validate_certs, + ) + except ValueError as e: + raise AnsibleError(str(e)) + + ret = [] + + try: + api_endpoint = f"/objects/bi_rule/{rule_id}" + response_content = api.get(api_endpoint) + response = json.loads(response_content) + + if "code" in response: + raise AnsibleError( + "Received error for %s - %s: %s" + % ( + response.get("url", ""), + response.get("code", ""), + response.get("msg", ""), + ) + ) + + ret.append(response) + + except Exception as e: + raise AnsibleError(f"Error fetching BI rule '{rule_id}': {str(e)}") + + return ret diff --git a/plugins/lookup/dcd.py b/plugins/lookup/dcd.py new file mode 100644 index 000000000..6a1811ebd --- /dev/null +++ b/plugins/lookup/dcd.py @@ -0,0 +1,253 @@ +# Copyright: (c) 2025, Lars Getwan +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ + name: dcd + author: Lars Getwan (@lgetwan) + version_added: "6.1.0" + + short_description: Retrieve details of a DCD Configuration. + + description: + - This lookup returns the details of a specified DCD configuration. + + options: + + _terms: + description: + - The ID of the DCD Configuration to retrieve. + required: True + + server_url: + description: URL of the Checkmk server + required: True + vars: + - name: ansible_lookup_checkmk_server_url + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SERVER_URL + ini: + - section: checkmk_lookup + key: server_url + + site: + description: Site name. + required: True + vars: + - name: ansible_lookup_checkmk_site + env: + - name: ANSIBLE_LOOKUP_CHECKMK_SITE + ini: + - section: checkmk_lookup + key: site + + auth_type: + description: + - The authentication type to use ('bearer', 'basic', 'cookie'). + required: False + default: 'bearer' + + + automation_user: + description: Automation user for the REST API access. + required: False + vars: + - name: ansible_lookup_checkmk_automation_user + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTOMATION_USER + ini: + - section: checkmk_lookup + key: automation_user + + automation_secret: + description: Automation secret for the REST API access. + required: False + vars: + - name: ansible_lookup_checkmk_automation_secret + env: + - name: ANSIBLE_LOOKUP_CHECKMK_AUTOMATION_SECRET + ini: + - section: checkmk_lookup + key: automation_secret + + auth_cookie: + description: + - The authentication cookie value if using cookie-based authentication. + required: False + + validate_certs: + description: + - Whether to validate SSL certificates. + required: False + type: bool + default: True + + notes: + - Like all lookups, this runs on the Ansible controller and is unaffected by other keywords such as 'become'. + - The directory of the play is used as the current working directory. + - It is B(NOT) possible to assign other variables to the variables mentioned in the C(vars) section! + This is a limitation of Ansible itself. +""" + +EXAMPLES = """ +- name: Get a DCD Configuration with a particular ID + ansible.builtin.debug: + msg: "DCD Configuration: {{ dcd_details }}" + vars: + dcd_details: "{{ + lookup('checkmk.general.dcd', + 'openshiftcluster02-dcd-object', + server_url='http://127.0.0.1:32001', + site='cmk', + auth_type='bearer', + automation_user='automation', + automation_secret='test123', + validate_certs=False + ) + }}" + +- name: "Use variables outside the module call." + ansible.builtin.debug: + msg: "DCD Configuration: {{ dcd_details }}" + vars: + ansible_lookup_checkmk_server_url: "http://127.0.0.1:32001" + ansible_lookup_checkmk_site: "mysite" + ansible_lookup_checkmk_automation_user: "myuser" + ansible_lookup_checkmk_automation_secret: "mysecret" + ansible_lookup_checkmk_validate_certs: false + dcd_details: "{{ lookup('checkmk.general.dcd', 'openshiftcluster02-dcd-object') }}" +""" + +RETURN = """ +_list: + description: + - A list containing the attributes of the DCD Configuration. + type: list + elements: dict +""" + +import base64 +import json + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible_collections.checkmk.general.plugins.module_utils.lookup_api import ( + CheckMKLookupAPI, +) + + +class ExtendedCheckmkAPI(CheckMKLookupAPI): + """ + Extends CheckMKLookupAPI to support 'basic' and 'cookie' authentication methods. + Ensures that Bearer authentication uses both 'automation_user' and 'automation_secret'. + """ + + def __init__( + self, + site_url, + auth_type="bearer", + automation_user=None, + automation_secret=None, + auth_cookie=None, + validate_certs=True, + ): + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + cookies = {} + + # Bearer Authentication: "Bearer USERNAME PASSWORD" + if auth_type == "bearer": + if not automation_user or not automation_secret: + raise ValueError( + "`automation_user` and `automation_secret` are required for bearer authentication." + ) + headers["Authorization"] = f"Bearer {automation_user} {automation_secret}" + + # Basic Authentication + elif auth_type == "basic": + if not automation_user or not automation_secret: + raise ValueError( + "`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + headers["Authorization"] = f"Basic {auth_b64}" + + # Cookie Authentication + elif auth_type == "cookie": + if not auth_cookie: + raise ValueError("`auth_cookie` is required for cookie authentication.") + cookies["auth_cmk"] = auth_cookie + + else: + raise ValueError(f"Unsupported `auth_type`: {auth_type}") + + super().__init__( + site_url=site_url, user="", secret="", validate_certs=validate_certs + ) + self.headers.update(headers) + self.cookies = cookies + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + self.set_options(var_options=variables, direct=kwargs) + + if not terms: + raise AnsibleError("No DCD ID provided for lookup.") + + dcd_id = terms[0] + + try: + server_url = self.get_option("server_url") + site = self.get_option("site") + auth_type = self.get_option("auth_type") or "bearer" + automation_user = self.get_option("automation_user") + automation_secret = self.get_option("automation_secret") + auth_cookie = self.get_option("auth_cookie") + validate_certs = self.get_option("validate_certs") + except KeyError as e: + raise AnsibleError(f"Missing required configuration option: {str(e)}") + + site_url = f"{server_url.rstrip('/')}/{site}" + + try: + api = ExtendedCheckmkAPI( + site_url=site_url, + auth_type=auth_type, + automation_user=automation_user, + automation_secret=automation_secret, + auth_cookie=auth_cookie, + validate_certs=validate_certs, + ) + except ValueError as e: + raise AnsibleError(str(e)) + + ret = [] + + try: + api_endpoint = f"/objects/dcd/{dcd_id}" + response_content = api.get(api_endpoint) + response = json.loads(response_content) + + if "code" in response and response.get("code") != 200: + raise AnsibleError( + "Received error for %s - %s: %s" + % ( + response.get("url", ""), + response.get("code", ""), + response.get("msg", ""), + ) + ) + + ret.append(response) + + except Exception as e: + raise AnsibleError(f"Error fetching DCD Configuration '{dcd_id}': {str(e)}") + + return ret diff --git a/plugins/module_utils/differ.py b/plugins/module_utils/differ.py new file mode 100644 index 000000000..bc35fa058 --- /dev/null +++ b/plugins/module_utils/differ.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ensure compatibility to Python2 +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json + + +class ConfigDiffer: + """ + Handles the normalization and comparison of configuration dictionaries. + """ + + def __init__(self, desired, current): + self.desired = desired + self.current = current + self.reference_keys = list(desired.keys()) + self.desired_cleaned = {} + self.current_cleaned = {} + + def normalize(self): + """ + Normalizes the desired and current configurations. + """ + self.desired_cleaned = self.clean_for_diff(self.desired.copy()) + self.current_cleaned = self.clean_for_diff(self.current.copy()) + + def needs_update(self): + """ + Determines whether an update is needed. + """ + self.normalize() + return self.desired_cleaned != self.current_cleaned + + def clean_for_diff(self, data): + """ + Cleans data dictionaries by keeping only keys present in reference_keys and normalizing the values. + """ + cleaned = {} + for key in self.reference_keys: + if key in data: + value = data[key] + if isinstance(value, dict): + cleaned[key] = self._normalize_dict(value) + elif isinstance(value, list): + cleaned[key] = self._normalize_list(value) + else: + cleaned[key] = self._normalize_value(value) + return cleaned + + def _normalize_value(self, value): + if isinstance(value, (bool, int)): + return value + elif isinstance(value, str): + return value.strip() + else: + return value + + def _normalize_list(self, value): + normalized_list = [] + for item in value: + if isinstance(item, dict): + normalized_list.append(self._normalize_dict(item)) + elif isinstance(item, list): + normalized_list.append(self._normalize_list(item)) + else: + normalized_list.append(self._normalize_value(item)) + return sorted(normalized_list, key=lambda x: json.dumps(x, sort_keys=True)) + + def _normalize_dict(self, data): + normalized_dict = {} + for k, v in data.items(): + if isinstance(v, dict): + normalized_dict[k] = self._normalize_dict(v) + elif isinstance(v, list): + normalized_dict[k] = self._normalize_list(v) + else: + normalized_dict[k] = self._normalize_value(v) + return normalized_dict + + def generate_diff(self, deletion=False): + """ + Generates a diff between the current and desired state. + """ + self.normalize() + + if deletion: + before = json.dumps(self.current_cleaned, indent=2, sort_keys=True) + "\n" + after = "{}" + "\n" + else: + before = json.dumps(self.current_cleaned, indent=2, sort_keys=True) + "\n" + after = json.dumps(self.desired_cleaned, indent=2, sort_keys=True) + "\n" + + return dict(before=before, after=after) diff --git a/plugins/module_utils/extended_api.py b/plugins/module_utils/extended_api.py new file mode 100644 index 000000000..19da7fbd0 --- /dev/null +++ b/plugins/module_utils/extended_api.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ensure compatibility to Python2 +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import base64 + +from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI + + +class ExtendedCheckmkAPI(CheckmkAPI): + """ + ExtendedCheckmkAPI adds support for multiple authentication methods: bearer, basic, and cookie. + """ + + def __init__(self, module): + super().__init__(module) + auth_type = self.params.get("auth_type", "bearer") + automation_user = self.params.get("automation_user") + automation_secret = self.params.get("automation_secret") + auth_cookie = self.params.get("auth_cookie") + + # Bearer Authentication + if auth_type == "bearer": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for bearer authentication." + ) + self.headers["Authorization"] = ( + f"Bearer {automation_user} {automation_secret}" + ) + + # Basic Authentication + elif auth_type == "basic": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + self.headers["Authorization"] = f"Basic {auth_b64}" + + # Cookie Authentication + elif auth_type == "cookie": + if not auth_cookie: + self.module.fail_json( + msg="`auth_cookie` is required for cookie authentication." + ) + self.cookies["auth_cmk"] = auth_cookie + + else: + self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") diff --git a/plugins/modules/bi_aggregation.py b/plugins/modules/bi_aggregation.py new file mode 100644 index 000000000..6c9aa557a --- /dev/null +++ b/plugins/modules/bi_aggregation.py @@ -0,0 +1,528 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: bi_aggregation + +short_description: Manage BI aggregations in Checkmk. + +version_added: "6.1.0" + +description: + - Manage BI aggregations within Checkmk. This module allows the creation, updating, and deletion of BI aggregations via the Checkmk API. + +extends_documentation_fragment: [checkmk.general.common] + +options: + aggregation: + description: + - Definition of the BI aggregation as needed by the Checkmk API. + type: dict + required: true + suboptions: + id: + description: + - The unique aggregation ID. + type: str + required: true + pack_id: + description: + - The identifier of the BI pack. + type: str + required: true + comment: + description: + - An optional comment that may be used to explain the purpose of this aggregation. + type: str + required: false + customer: + description: + - The customer ID for this aggregation (CME Edition only). + type: str + required: false + groups: + description: + - Groups associated with the aggregation. + type: dict + required: false + suboptions: + names: + description: + - List of group names. + type: list + elements: str + required: false + paths: + description: + - List of group paths. + type: list + elements: list + required: false + node: + description: + - Node generation definition. + type: dict + required: true + suboptions: + search: + description: + - Search criteria. + type: dict + required: true + action: + description: + - Action on search results. + type: dict + required: true + aggregation_visualization: + description: + - Aggregation visualization options. + type: dict + required: false + computation_options: + description: + - Computation options. + type: dict + required: false + state: + description: + - State of the BI aggregation. + choices: [present, absent] + default: present + type: str + +author: + - Lars Getwan (@lgetwan) +""" + +EXAMPLES = r""" +- name: Create a BI aggregation with state_of_host + checkmk.general.bi_aggregation: + server_url: "http://myserver/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + aggregation: + id: "aggr1" + pack_id: "pack1" + comment: "Aggregation comment" + customer: "customer1" + groups: + names: ["groupA", "groupB"] + paths: + - ["path", "group", "a"] + - ["path", "group", "b"] + node: + search: + type: "empty" + action: + type: "state_of_host" + rule_id: "rule123" + host_regex: ".*" + aggregation_visualization: + ignore_rule_styles: false + layout_id: "builtin_default" + line_style: "round" + computation_options: + disabled: false + use_hard_states: false + escalate_downtimes_as_warn: false + freeze_aggregations: false + state: "present" + +- name: Delete a BI aggregation + checkmk.general.bi_aggregation: + server_url: "http://myserver/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + aggregation: + id: "aggr1" + pack_id: "pack1" + state: "absent" +""" + +RETURN = r""" +msg: + description: The output message that the module generates. + type: str + returned: always + sample: 'BI aggregation created.' +http_code: + description: The HTTP code the Checkmk API returns. + type: int + returned: always + sample: 200 +content: + description: The complete created/changed BI aggregation. + returned: when the BI aggregation is created or updated. + type: dict +""" + +import base64 +import json + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer + + +class ExtendedCheckmkAPI(CheckmkAPI): + """ + Extends CheckmkAPI to support 'basic' and 'cookie' authentication methods. + Ensures that Bearer authentication uses both 'automation_user' and 'automation_secret'. + """ + + def __init__(self, module): + """Initialize ExtendedCheckmkAPI with authentication handling.""" + super().__init__(module) + auth_type = self.params.get("auth_type", "bearer") + automation_user = self.params.get("automation_user") + automation_secret = self.params.get("automation_secret") + auth_cookie = self.params.get("auth_cookie") + + if auth_type == "bearer": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for bearer authentication." + ) + self.headers["Authorization"] = ( + f"Bearer {automation_user} {automation_secret}" + ) + elif auth_type == "basic": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + self.headers["Authorization"] = f"Basic {auth_b64}" + elif auth_type == "cookie": + if not auth_cookie: + self.module.fail_json( + msg="`auth_cookie` is required for cookie authentication." + ) + self.cookies["auth_cmk"] = auth_cookie + else: + self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") + + +class BIAggregationHTTPCodes: + """ + Defines HTTP status codes and corresponding actions for BI aggregation operations. + """ + + get = { + 200: (False, False, "BI aggregation found, nothing changed"), + 404: (False, False, "BI aggregation not found"), + } + create = { + 200: (True, False, "BI aggregation created"), + 201: (True, False, "BI aggregation created"), + 204: (True, False, "BI aggregation created"), + } + edit = { + 200: (True, False, "BI aggregation modified"), + } + delete = { + 204: (True, False, "BI aggregation deleted"), + } + + +class BIAggregationAPI(ExtendedCheckmkAPI): + """ + Manages BI aggregation operations via the Checkmk API. + """ + + def __init__(self, module): + """Initialize BIAggregationAPI with module parameters. + + Args: + module (AnsibleModule): The Ansible module object. + """ + super().__init__(module) + aggregation = self.params.get("aggregation") + if not aggregation or "id" not in aggregation or "pack_id" not in aggregation: + self.module.fail_json( + msg="Missing 'id' or 'pack_id' in aggregation dictionary" + ) + + self.aggregation_id = aggregation["id"] + self.pack_id = aggregation["pack_id"] + self.desired = aggregation.copy() + + self.state = None + self._get_current() + + # Initialize the ConfigDiffer with desired and current configurations + self.differ = ConfigDiffer(self.desired, self.current) + + def _get_current(self): + """ + Retrieve the current state of the BI aggregation from the Checkmk API. + """ + endpoint = self._build_endpoint(action="get") + result = self._fetch( + code_mapping=BIAggregationHTTPCodes.get, + endpoint=endpoint, + method="GET", + ) + + if result.http_code == 200: + self.state = "present" + try: + self.current = json.loads(result.content) + except json.JSONDecodeError: + self.module.fail_json( + msg="Failed to decode JSON response from API.", + content=result.content, + ) + else: + self.state = "absent" + self.current = {} + + def _build_endpoint(self, action="get"): + """ + Build the API endpoint URL for the BI aggregation. + + Args: + action (str): The action being performed ('get', 'create', 'edit', 'delete'). + + Returns: + str: API endpoint URL. + """ + if action in ["get", "create", "edit", "delete"]: + return f"/objects/bi_aggregation/{self.aggregation_id}" + else: + self.module.fail_json( + msg=f"Unsupported action '{action}' for building endpoint." + ) + + def needs_update(self): + """ + Determine whether an update to the BI aggregation is needed. + + Returns: + bool: True if changes are needed, False otherwise. + """ + return self.differ.needs_update() + + def generate_diff(self, deletion=False): + """ + Generates a diff between the current and desired state. + + Args: + deletion (bool): Whether the diff is for a deletion. + + Returns: + dict: Dictionary containing 'before' and 'after' states. + """ + return self.differ.generate_diff(deletion) + + def _perform_action(self, action, method, data=None): + """ + Helper method to perform CRUD actions. + + Args: + action (str): The action being performed ('create', 'edit', 'delete'). + method (str): The HTTP method. + data (dict, optional): The data to send with the request. + + Returns: + dict: The result dictionary. + """ + endpoint = self._build_endpoint(action=action) + + diff = None + if self.module._diff: + deletion_flag = action == "delete" + diff = self.generate_diff(deletion=deletion_flag) + + if self.module.check_mode: + action_msgs = {"create": "created", "edit": "modified", "delete": "deleted"} + return dict( + msg=f"BI aggregation would be {action_msgs.get(action, action)}.", + changed=True, + diff=diff, + ) + + response = self._fetch( + code_mapping=getattr(BIAggregationHTTPCodes, action), + endpoint=endpoint, + data=data, + method=method, + ) + + if response.failed: + self.module.fail_json(msg=response.msg, content=response.content) + + result_dict = { + "changed": response.changed, + "msg": response.msg, + "http_code": response.http_code, + "content": json.loads(response.content) if response.content else {}, + } + + if diff: + result_dict["diff"] = diff + + return result_dict + + def create(self): + """ + Create a new BI aggregation via the Checkmk API. + + Returns: + dict: The result of the creation operation. + """ + return self._perform_action(action="create", method="POST", data=self.desired) + + def edit(self): + """ + Update an existing BI aggregation via the Checkmk API. + + Returns: + dict: The result of the update operation. + """ + return self._perform_action(action="edit", method="PUT", data=self.desired) + + def delete(self): + """ + Delete an existing BI aggregation via the Checkmk API. + + Returns: + dict: The result of the deletion operation. + """ + return self._perform_action(action="delete", method="DELETE") + + +def run_module(): + """ + The main logic for the Ansible module. + + This function defines the module parameters, initializes the BIAggregationAPI, and performs + the appropriate action (create, edit, delete) based on the state of the BI aggregation. + + Returns: + None: The result is returned to Ansible via module.exit_json(). + """ + module_args = dict( + server_url=dict(type="str", required=True), + site=dict(type="str", required=True), + auth_type=dict( + type="str", + choices=["bearer", "basic", "cookie"], + default="bearer", + required=False, + ), + automation_user=dict(type="str", required=False), + automation_secret=dict(type="str", required=False, no_log=True), + auth_cookie=dict(type="str", required=False, no_log=True), + aggregation=dict( + type="dict", + required=True, + options=dict( + id=dict(type="str", required=True), + pack_id=dict(type="str", required=True), + comment=dict(type="str", required=False), + customer=dict(type="str", required=False), + groups=dict( + type="dict", + required=False, + options=dict( + names=dict(type="list", elements="str", required=False), + paths=dict( + type="list", + elements="list", + required=False, + ), + ), + ), + node=dict( + type="dict", + required=False, + options=dict( + search=dict(type="dict", required=True), + action=dict(type="dict", required=True), + ), + ), + aggregation_visualization=dict( + type="dict", + required=False, + ), + computation_options=dict( + type="dict", + required=False, + ), + ), + ), + state=dict(type="str", default="present", choices=["present", "absent"]), + validate_certs=dict(type="bool", default=True, required=False), + ) + + required_if = [ + ("auth_type", "bearer", ["automation_user", "automation_secret"]), + ("auth_type", "basic", ["automation_user", "automation_secret"]), + ("auth_type", "cookie", ["auth_cookie"]), + ] + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=required_if, + ) + + desired_state = module.params.get("state") + aggregation = module.params.get("aggregation") + + # Initialize BIAggregationAPI + bi_aggregation_api = BIAggregationAPI(module) + + try: + if desired_state == "present": + if bi_aggregation_api.state == "absent": + result = bi_aggregation_api.create() + elif bi_aggregation_api.needs_update(): + result = bi_aggregation_api.edit() + else: + result = dict( + changed=False, + msg="BI aggregation is already in the desired state.", + ) + elif desired_state == "absent": + if bi_aggregation_api.state == "present": + result = bi_aggregation_api.delete() + else: + result = dict( + changed=False, + msg="BI aggregation is already absent.", + ) + except Exception as e: + module.fail_json(msg=f"Error managing the BI aggregation: {e}") + + module.exit_json(**result) + + +def main(): + """ + Main entry point for the module. + + This function is invoked when the module is executed directly. + + Returns: + None: Calls run_module() to handle the logic. + """ + run_module() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/bi_pack.py b/plugins/modules/bi_pack.py new file mode 100644 index 000000000..083abddc2 --- /dev/null +++ b/plugins/modules/bi_pack.py @@ -0,0 +1,453 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: bi_pack + +short_description: Manage BI packs in Checkmk. + +version_added: "6.1.0" + +description: + - Manage BI packs within Checkmk. This module allows the creation, updating, and deletion of BI packs via the Checkmk API. + +extends_documentation_fragment: [checkmk.general.common] + +options: + + pack: + description: Definition of the BI pack as required by the Checkmk API. + type: dict + required: true + suboptions: + id: + description: Unique identifier for the BI pack. + type: str + required: true + + title: + description: Title of the BI pack. + type: str + required: true + + contact_groups: + description: List of contact groups associated with the BI pack. + type: list + elements: str + default: [] + required: false + + public: + description: Whether the BI pack is public or not. + type: bool + default: false + required: false + + state: + description: + - Desired state of the BI pack. + Use `present` to create or update the BI pack. + Use `absent` to delete the BI pack. + type: str + default: "present" + choices: + - present + - absent + +author: + - Lars Getwan (@lgetwan) +""" + +EXAMPLES = r""" +- name: Create a BI pack with Bearer Authentication + bi_pack: + server_url: "https://checkmk.example.com/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + pack: + id: "cluster_pack" + title: "Cluster Pack" + contact_groups: + - "Admins" + - "DevOps" + public: true + state: "present" + +- name: Create a BI pack with Basic Authentication + bi_pack: + server_url: "https://checkmk.example.com/" + site: "mysite" + auth_type: "basic" + automation_user: "basicuser" + automation_secret: "basicpassword" + pack: + id: "network_pack" + title: "Network Pack" + contact_groups: + - "NetworkAdmins" + public: false + state: "present" + +- name: Create a BI pack with Cookie Authentication + bi_pack: + server_url: "https://checkmk.example.com/" + site: "mysite" + auth_type: "cookie" + auth_cookie: "sessionid=abc123xyz" + pack: + id: "storage_pack" + title: "Storage Pack" + contact_groups: + - "StorageAdmins" + public: true + state: "present" + +- name: Delete a BI pack + bi_pack: + server_url: "https://checkmk.example.com/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + pack: + id: "cluster_pack" + state: "absent" +""" + +RETURN = r""" +msg: + description: The output message that the module generates. Contains the API status details in case of an error. + type: str + returned: always + sample: 'BI pack created.' + +http_code: + description: The HTTP code the Checkmk API returns. + type: int + returned: always + sample: 200 + +content: + description: The complete created/changed BI pack. + returned: when the BI pack is created or updated. + type: dict +""" + +import json + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer + + +class BIPackHTTPCodes: + """ + Defines HTTP status codes and corresponding actions for BI pack operations. + """ + + get = { + 200: (False, False, "BI pack found, nothing changed"), + 404: (False, False, "BI pack not found"), + } + create = { + 200: (True, False, "BI pack created"), + 201: (True, False, "BI pack created"), + 204: (True, False, "BI pack created"), + } + edit = { + 200: (True, False, "BI pack modified"), + } + delete = { + 204: (True, False, "BI pack deleted"), + } + + +class BIPackAPIHandler(CheckmkAPI): + """ + Manages BI pack operations via the Checkmk API. + """ + + def __init__(self, module): + """Initialize BIPackAPIHandler with module parameters.""" + super().__init__(module) + pack = module.params.get("pack") + if not pack or "id" not in pack: + self.module.fail_json(msg="Missing 'id' in pack dictionary") + + self.pack_id = pack["id"] + self.desired = { + "title": pack["title"], + "contact_groups": pack.get("contact_groups"), + "public": pack.get("public"), + } + + self.state = None + self._get_current() + + # Initialize the ConfigDiffer with desired and current configurations + self.differ = ConfigDiffer(self.desired, self.current) + + def _get_current(self): + """ + Fetches the current state of the BI pack from the Checkmk API. + """ + endpoint = self._build_endpoint(action="get") + response = self._fetch( + code_mapping=BIPackHTTPCodes.get, + endpoint=endpoint, + method="GET", + ) + + if response.http_code == 200: + self.state = "present" + try: + api_response = json.loads(response.content) + except json.JSONDecodeError: + self.module.fail_json( + msg="Failed to decode JSON response from API.", + content=response.content, + ) + + # Extrahiere relevante Felder aus 'extensions' + extensions = api_response.get("extensions", {}) + + self.current = { + "title": extensions.get("title", api_response.get("title", "")), + "contact_groups": extensions.get( + "contact_groups", api_response.get("contact_groups", []) + ), + "public": extensions.get("public", api_response.get("public", False)), + } + else: + self.state = "absent" + self.current = {} + + def _build_endpoint(self, action): + """ + Constructs the API endpoint for the BI pack based on the action. + + Args: + action (str): The action being performed ('get', 'create', 'edit', 'delete'). + + Returns: + str: The full API endpoint URL for the BI pack. + + Raises: + AnsibleModule.fail_json: If an unsupported action is provided. + """ + supported_actions = ["get", "create", "edit", "delete"] + if action in supported_actions: + return f"/objects/bi_pack/{self.pack_id}" + else: + self.module.fail_json( + msg=f"Unsupported action '{action}' for building endpoint." + ) + + def needs_update(self): + """ + Determines whether an update to the BI rule is needed. + + Returns: + bool: True if changes are needed, False otherwise. + """ + return self.differ.needs_update() + + def generate_diff(self, deletion=False): + """ + Generates a diff output to show changes between the current and desired state. + + Args: + deletion (bool): If True, generate a diff for a deletion. + + Returns: + dict: A dictionary containing the 'before' and 'after' states of the BI pack. + """ + return self.differ.generate_diff(deletion=deletion) + + def _perform_action(self, action, method, data=None): + """ + Helper method to perform CRUD actions. + + Args: + action (str): The action being performed ('create', 'edit', 'delete'). + method (str): The HTTP method. + data (dict, optional): The data to send with the request. + + Returns: + dict: The result dictionary. + """ + endpoint = self._build_endpoint(action=action) + + diff = None + if self.module._diff: + deletion_flag = action == "delete" + diff = self.generate_diff(deletion=deletion_flag) + + if self.module.check_mode: + action_msgs = {"create": "created", "edit": "modified", "delete": "deleted"} + return dict( + msg=f"BI pack would be {action_msgs.get(action, action)}.", + changed=True, + diff=diff, + ) + + response = self._fetch( + code_mapping=getattr(BIPackHTTPCodes, action), + endpoint=endpoint, + data=data, + method=method, + ) + + if response.failed: + self.module.fail_json(msg=response.msg, content=response.content) + + result_dict = { + "changed": response.changed, + "msg": response.msg, + "http_code": response.http_code, + "content": json.loads(response.content) if response.content else {}, + } + + if diff: + result_dict["diff"] = diff + + return result_dict + + def create(self): + """ + Creates a new BI pack via the Checkmk API. + + Returns: + dict: The result of the creation operation. + """ + return self._perform_action(action="create", method="POST", data=self.desired) + + def edit(self): + """ + Updates an existing BI pack via the Checkmk API. + + Returns: + dict: The result of the update operation. + """ + return self._perform_action(action="edit", method="PUT", data=self.desired) + + def delete(self): + """ + Deletes an existing BI pack via the Checkmk API. + + Returns: + dict: The result of the deletion operation. + """ + return self._perform_action(action="delete", method="DELETE") + + +def run_module(): + """ + The main logic for the Ansible module. + + This function defines the module parameters, initializes the BIPackAPIHandler, and performs + the appropriate action (create, edit, delete) based on the state of the BI pack. + + Returns: + None: The result is returned to Ansible via module.exit_json(). + """ + module_args = dict( + server_url=dict(type="str", required=True), + site=dict(type="str", required=True), + auth_type=dict( + type="str", + choices=["bearer", "basic", "cookie"], + default="bearer", + required=False, + ), + automation_user=dict(type="str", required=False), + automation_secret=dict(type="str", required=False, no_log=True), + auth_cookie=dict(type="str", required=False, no_log=True), + pack=dict( + type="dict", + required=True, + options=dict( + id=dict(type="str", required=True), + title=dict(type="str", required=False), + contact_groups=dict( + type="list", elements="str", default=[], required=False + ), + public=dict(type="bool", default=False, required=False), + ), + ), + state=dict(type="str", default="present", choices=["present", "absent"]), + validate_certs=dict(type="bool", default=True, required=False), + ) + + required_if = [ + ("auth_type", "bearer", ["automation_user", "automation_secret"]), + ("auth_type", "basic", ["automation_user", "automation_secret"]), + ("auth_type", "cookie", ["auth_cookie"]), + ] + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=required_if, + ) + + desired_state = module.params.get("state") + pack = module.params.get("pack") + + # Additional validation: 'title' is required when 'state' is 'present' + if desired_state == "present": + if "title" not in pack or not pack["title"]: + module.fail_json( + msg="'title' is required in 'pack' when state is 'present'." + ) + + bipack_api = BIPackAPIHandler(module) + + try: + if desired_state == "present": + if bipack_api.state == "absent": + result = bipack_api.create() + elif bipack_api.needs_update(): + result = bipack_api.edit() + else: + result = dict( + changed=False, + msg="BI pack is already in the desired state.", + ) + elif desired_state == "absent": + if bipack_api.state == "present": + result = bipack_api.delete() + else: + result = dict( + changed=False, + msg="BI pack is already absent.", + ) + except Exception as e: + module.fail_json(msg=f"Error managing the BI pack: {e}") + + module.exit_json(**result) + + +def main(): + """ + Main entry point for the module. + + This function is invoked when the module is executed directly. + + Returns: + None: Calls run_module() to handle the logic. + """ + run_module() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/bi_rule.py b/plugins/modules/bi_rule.py new file mode 100644 index 000000000..540d1c396 --- /dev/null +++ b/plugins/modules/bi_rule.py @@ -0,0 +1,545 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: bi_rule + +short_description: Manage BI rules in Checkmk. + +version_added: "6.1.0" + +description: + - Manage BI rules in Checkmk, including creation, updating, and deletion. + +extends_documentation_fragment: [checkmk.general.common] + +options: + rule: + type: dict + required: true + description: Definition of the BI rule as needed by the Checkmk API. + suboptions: + pack_id: + type: str + required: true + description: The identifier of the BI pack. + id: + type: str + required: true + description: The unique BI rule ID. + nodes: + type: list + elements: dict + required: true + description: List of nodes associated with the BI rule. + properties: + type: dict + required: true + description: Properties of the BI rule. + aggregation_function: + type: dict + required: true + description: Aggregation function configuration. + computation_options: + type: dict + required: true + description: Computation options for the BI rule. + node_visualization: + type: dict + required: true + description: Visualization options for the BI rule nodes. + params: + type: dict + required: false + description: Additional parameters for the BI rule. + suboptions: + arguments: + type: list + elements: str + required: false + description: List of arguments for the BI rule. + + state: + type: str + default: "present" + choices: ["present", "absent"] + description: State of the BI rule. + +author: + - Lars Getwan (@lgetwan) +""" + +EXAMPLES = r""" +- name: Create a BI rule + checkmk.general.bi_rule: + server_url: "https://example.com/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + rule: + pack_id: "cluster_pack" + id: "testrule1" + nodes: + - search: + type: "empty" + action: + type: "call_a_rule" + rule_id: "test-child-rule1" + params: + arguments: [] + properties: + title: "Test Rule 1" + comment: "" + docu_url: "" + icon: "" + state_messages: {} + aggregation_function: + type: "best" + count: 1 + restrict_state: 2 + computation_options: + disabled: false + node_visualization: + type: "block" + style_config: {} + state: "present" + +- name: Delete a BI rule + checkmk.general.bi_rule: + server_url: "https://example.com/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + rule: + pack_id: "cluster_pack" + id: "testrule1" + state: "absent" +""" + +RETURN = r""" +msg: + description: + - The output message that the module generates. Contains the API status details in case of an error. + type: str + returned: always + sample: 'BI rule created.' + +http_code: + description: + - The HTTP code the Checkmk API returns. + type: int + returned: always + sample: 200 + +content: + description: + - The complete created/changed BI rule. + returned: when the BI rule is created or updated. + type: dict +""" + +import base64 +import json + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer + + +class ExtendedCheckmkAPI(CheckmkAPI): + """ + Extends CheckmkAPI to support 'basic' and 'cookie' authentication methods. + Ensures that Bearer authentication uses both 'automation_user' and 'automation_secret'. + """ + + def __init__(self, module): + """Initialize ExtendedCheckmkAPI with authentication handling.""" + super().__init__(module) + auth_type = self.params.get("auth_type", "bearer") + automation_user = self.params.get("automation_user") + automation_secret = self.params.get("automation_secret") + auth_cookie = self.params.get("auth_cookie") + + if auth_type == "bearer": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for bearer authentication." + ) + self.headers["Authorization"] = ( + f"Bearer {automation_user} {automation_secret}" + ) + elif auth_type == "basic": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + self.headers["Authorization"] = f"Basic {auth_b64}" + elif auth_type == "cookie": + if not auth_cookie: + self.module.fail_json( + msg="`auth_cookie` is required for cookie authentication." + ) + self.cookies["auth_cmk"] = auth_cookie + else: + self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") + + +class BIRuleHTTPCodes: + """ + BIRuleHTTPCodes defines the HTTP status codes and corresponding messages + for BI rule operations such as GET, CREATE, EDIT, and DELETE. + """ + + get = { + 200: (False, False, "BI rule found, nothing changed"), + 404: (False, False, "BI rule not found"), + } + create = { + 200: (True, False, "BI rule created"), + 201: (True, False, "BI rule created"), + 204: (True, False, "BI rule created"), + } + edit = { + 200: (True, False, "BI rule modified"), + } + delete = { + 204: (True, False, "BI rule deleted"), + } + + +class BIRuleAPI(ExtendedCheckmkAPI): + """ + Manages BI rule operations via the Checkmk API. + """ + + def __init__(self, module): + """Initialize BIRuleAPI with module parameters. + + Args: + module (AnsibleModule): The Ansible module object. + """ + super().__init__(module) + rule = self.params.get("rule") + if not rule or "id" not in rule or "pack_id" not in rule: + self.module.fail_json(msg="Missing 'id' or 'pack_id' in rule dictionary") + + self.rule_id = rule["id"] + self.pack_id = rule["pack_id"] + self.desired = rule.copy() + + self.state = None + self._get_current() + + # Initialize the ConfigDiffer with desired and current configurations + self.differ = ConfigDiffer(self.desired, self.current) + + def _get_current(self): + """ + Retrieves the current state of the BI rule from the Checkmk API. + """ + endpoint = self._build_endpoint(action="get") + result = self._fetch( + code_mapping=BIRuleHTTPCodes.get, + endpoint=endpoint, + method="GET", + ) + + if result.http_code == 200: + self.state = "present" + try: + self.current = json.loads(result.content) + except json.JSONDecodeError: + self.module.fail_json( + msg="Failed to decode JSON response from API.", + content=result.content, + ) + else: + self.state = "absent" + self.current = {} + + def _build_endpoint(self, action): + """ + Builds the API endpoint URL for the BI rule. + + Args: + action (str): The action being performed ('get', 'create', 'edit', 'delete'). + + Returns: + str: API endpoint URL. + """ + supported_actions = ["get", "create", "edit", "delete"] + if action in supported_actions: + return f"/objects/bi_rule/{self.rule_id}" + else: + self.module.fail_json( + msg=f"Unsupported action '{action}' for building endpoint." + ) + + def needs_update(self): + """ + Determines whether an update to the BI rule is needed. + + Returns: + bool: True if changes are needed, False otherwise. + """ + return self.differ.needs_update() + + def generate_diff(self, deletion=False): + """ + Generates a diff between the current and desired state. + + Args: + deletion (bool): Whether the diff is for a deletion. + + Returns: + dict: Dictionary containing 'before' and 'after' states. + """ + return self.differ.generate_diff(deletion) + + def _perform_action(self, action, method, data=None): + """ + Helper method to perform CRUD actions. + + Args: + action (str): The action being performed ('create', 'edit', 'delete'). + method (str): The HTTP method. + data (dict, optional): The data to send with the request. + + Returns: + dict: The result dictionary. + """ + endpoint = self._build_endpoint(action=action) + + diff = None + if self.module._diff: + deletion_flag = action == "delete" + diff = self.generate_diff(deletion=deletion_flag) + + if self.module.check_mode: + action_msgs = {"create": "created", "edit": "modified", "delete": "deleted"} + return dict( + msg=f"BI rule would be {action_msgs.get(action, action)}.", + changed=True, + diff=diff, + ) + + response = self._fetch( + code_mapping=getattr(BIRuleHTTPCodes, action), + endpoint=endpoint, + data=data, + method=method, + ) + + if response.failed: + self.module.fail_json(msg=response.msg, content=response.content) + + result_dict = { + "changed": response.changed, + "msg": response.msg, + "http_code": response.http_code, + "content": json.loads(response.content) if response.content else {}, + } + + if diff: + result_dict["diff"] = diff + + return result_dict + + def create(self): + """ + Creates a new BI rule via the Checkmk API. + + Returns: + dict: The result of the creation operation. + """ + return self._perform_action(action="create", method="POST", data=self.desired) + + def edit(self): + """ + Updates an existing BI rule via the Checkmk API. + + Returns: + dict: The result of the update operation. + """ + return self._perform_action(action="edit", method="PUT", data=self.desired) + + def delete(self): + """ + Deletes an existing BI rule via the Checkmk API. + + Returns: + dict: The result of the deletion operation. + """ + return self._perform_action(action="delete", method="DELETE") + + +def run_module(): + """ + The main logic for the Ansible module. + + This function defines the module parameters, initializes the BIRuleAPI, and performs + the appropriate action (create, edit, delete) based on the state of the BI rule. + + Returns: + None: The result is returned to Ansible via module.exit_json(). + """ + module_args = dict( + server_url=dict(type="str", required=True), + site=dict(type="str", required=True), + auth_type=dict( + type="str", + choices=["bearer", "basic", "cookie"], + default="bearer", + required=False, + ), + automation_user=dict( + type="str", + required=False, + description="The automation user for API authentication.", + ), + automation_secret=dict( + type="str", + required=False, + no_log=True, + description="The automation secret (password) for API authentication.", + ), + auth_cookie=dict( + type="str", + required=False, + no_log=True, + description="The authentication cookie for API authentication.", + ), + rule=dict( + type="dict", + required=True, + description="Definition of the BI rule as needed by the Checkmk API.", + options=dict( + pack_id=dict( + type="str", + required=True, + description="The identifier of the BI pack.", + ), + id=dict( + type="str", required=True, description="The unique BI rule ID." + ), + nodes=dict( + type="list", + elements="dict", + required=True, + description="List of nodes associated with the BI rule.", + ), + properties=dict( + type="dict", required=True, description="Properties of the BI rule." + ), + aggregation_function=dict( + type="dict", + required=True, + description="Aggregation function configuration.", + ), + computation_options=dict( + type="dict", + required=True, + description="Computation options for the BI rule.", + ), + node_visualization=dict( + type="dict", + required=True, + description="Visualization options for the BI rule nodes.", + ), + params=dict( + type="dict", + required=False, + description="Additional parameters for the BI rule.", + options=dict( + arguments=dict( + type="list", + elements="str", + required=False, + description="List of arguments for the BI rule.", + ), + ), + ), + ), + ), + state=dict( + type="str", + default="present", + choices=["present", "absent"], + description="State of the BI rule.", + ), + validate_certs=dict( + type="bool", + default=True, + required=False, + description="Whether to validate SSL certificates.", + ), + ) + + required_if = [ + ("auth_type", "bearer", ["automation_user", "automation_secret"]), + ("auth_type", "basic", ["automation_user", "automation_secret"]), + ("auth_type", "cookie", ["auth_cookie"]), + ] + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=required_if, + ) + + desired_state = module.params.get("state") + rule = module.params.get("rule") + + # Initialize BIRuleAPI + bi_rule_api = BIRuleAPI(module) + + try: + if desired_state == "present": + if bi_rule_api.state == "absent": + result = bi_rule_api.create() + elif bi_rule_api.needs_update(): + result = bi_rule_api.edit() + else: + result = dict( + changed=False, + msg="BI rule is already in the desired state.", + ) + elif desired_state == "absent": + if bi_rule_api.state == "present": + result = bi_rule_api.delete() + else: + result = dict( + changed=False, + msg="BI rule is already absent.", + ) + except Exception as e: + module.fail_json(msg=f"Error managing the BI rule: {e}") + + module.exit_json(**result) + + +def main(): + """ + Main entry point for the module. + + This function is invoked when the module is executed directly. + + Returns: + None: Calls run_module() to handle the logic. + """ + run_module() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/dcd.py b/plugins/modules/dcd.py new file mode 100644 index 000000000..4e776e2e8 --- /dev/null +++ b/plugins/modules/dcd.py @@ -0,0 +1,625 @@ +#!/usr/bin/python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: dcd + +short_description: Manage Dynamic Configuration Definitions in Checkmk. + +version_added: "6.1.0" + +description: + - Manage Dynamic Configuration Definitions (DCD) in Checkmk, including creation, updating, and deletion. + +extends_documentation_fragment: [checkmk.general.common] + +options: + + dcd_config: + description: + - Configuration parameters for the DCD. + type: dict + required: true + options: + dcd_id: + description: + - Identifier for the DCD configuration. + type: str + required: true + title: + description: + - Title of the DCD. + type: str + required: false + comment: + description: + - Description or comment for the DCD. + type: str + default: "" + site: + description: + - Name of the Checkmk site for the DCD configuration. + type: str + required: true + connector_type: + description: + - Type of connector (e.g., "piggyback"). + type: str + default: piggyback + interval: + description: + - Interval in seconds for DCD polling. + type: int + default: 60 + creation_rules: + description: + - Rules for creating hosts. + type: list + elements: dict + options: + folder_path: + description: + - Folder path for host creation. + type: str + required: true + delete_hosts: + description: + - Whether to delete hosts that no longer match. + type: bool + default: false + host_attributes: + description: + - Additional host attributes to set on created hosts. + type: dict + discover_on_creation: + description: + - Discover services on host creation. + type: bool + default: true + restrict_source_hosts: + description: + - List of source hosts to restrict the DCD to. + type: list + elements: str + state: + description: + - Desired state of the DCD. + type: str + choices: + - present + - absent + default: present + +author: + - Lars Getwan (@lgetwan) +""" + +EXAMPLES = r""" +- name: Create a DCD configuration + checkmk.general.dcd: + server_url: "http://myserver/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + dcd_config: + dcd_id: "PiggybackCluster1" + title: "Piggyback Configuration for Cluster1" + comment: "Piggyback config for Cluster1 host" + site: "mysite" + connector_type: "piggyback" + interval: 5 + creation_rules: + - folder_path: "/cluster1" + delete_hosts: false + host_attributes: + tag_address_family: "no-ip" + tag_agent: "special-agents" + tag_piggyback: "piggyback" + tag_snmp_ds: "no-snmp" + discover_on_creation: true + restrict_source_hosts: + - "cluster1" + state: "present" + +- name: Delete a DCD configuration + checkmk.general.dcd: + server_url: "http://myserver/" + site: "mysite" + auth_type: "bearer" + automation_user: "myuser" + automation_secret: "mysecret" + dcd_config: + dcd_id: "PiggybackCluster1" + site: "mysite" + state: "absent" +""" + +RETURN = r""" +msg: + description: + - The output message that the module generates. + type: str + returned: always +http_code: + description: + - HTTP code returned by the Checkmk API. + type: int + returned: always +content: + description: + - Content of the DCD object. + returned: when state is present and DCD created or updated. + type: dict +diff: + description: + - The diff between the current and desired state. + type: dict + returned: when differences are detected or in diff mode +""" + +import base64 +import json + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer + + +class ExtendedCheckmkAPI(CheckmkAPI): + """ + Extends CheckmkAPI to support bearer, basic, and cookie authentication methods. + """ + + def __init__(self, module): + super().__init__(module) + auth_type = self.params.get("auth_type", "bearer") + automation_user = self.params.get("automation_user") + automation_secret = self.params.get("automation_secret") + auth_cookie = self.params.get("auth_cookie") + + if auth_type == "bearer": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for bearer authentication." + ) + self.headers["Authorization"] = ( + f"Bearer {automation_user} {automation_secret}" + ) + + elif auth_type == "basic": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_b64 = base64.b64encode( + f"{automation_user}:{automation_secret}".encode() + ).decode() + self.headers["Authorization"] = f"Basic {auth_b64}" + + elif auth_type == "cookie": + if not auth_cookie: + self.module.fail_json( + msg="`auth_cookie` is required for cookie authentication." + ) + self.cookies["auth_cmk"] = auth_cookie + + else: + self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") + + +class DCDHTTPCodes: + """ + DCDHTTPCodes defines the HTTP status codes and corresponding messages + for DCD operations such as GET, CREATE, EDIT, and DELETE. + """ + + get = { + 200: (False, False, "DCD configuration found, nothing changed"), + 404: (False, False, "DCD configuration not found"), + } + create = { + 200: (True, False, "DCD configuration created"), + 201: (True, False, "DCD configuration created"), + 204: (True, False, "DCD configuration created"), + 405: (False, True, "Method Not Allowed"), + } + edit = { + 200: (True, False, "DCD configuration modified"), + 405: (False, True, "Method Not Allowed"), + } + delete = { + 204: (True, False, "DCD configuration deleted"), + 405: (False, True, "Method Not Allowed"), + } + + +class DCDAPI(ExtendedCheckmkAPI): + """ + Manages DCD operations via the Checkmk API. + """ + + def __init__(self, module): + """ + Initializes the DCDAPI class, retrieves the current state of the DCD configuration. + + Args: + module (AnsibleModule): The Ansible module object. + """ + super().__init__(module) + dcd_config = self.params.get("dcd_config") + if not dcd_config or "dcd_id" not in dcd_config: + self.module.fail_json(msg="Missing 'dcd_id' in dcd_config dictionary") + + self.dcd_id = dcd_config["dcd_id"] + self.desired = dcd_config.copy() + + # Ensure 'site' is present in the desired state + if "site" not in self.desired or self.desired["site"] is None: + self.desired["site"] = self.params.get("site") + if self.desired["site"] is None: + self.module.fail_json(msg="`site` is required in dcd_config.") + + # Ensure 'comment' is not null + if "comment" not in self.desired or self.desired["comment"] is None: + self.desired["comment"] = "" + + self.state = None + + self._get_current() + + # Initialize the ConfigDiffer with desired and current configurations + self.differ = ConfigDiffer(self.desired, self.current) + + def _get_current(self): + """ + Retrieves the current state of the DCD configuration from the Checkmk API. + """ + endpoint = self._build_endpoint(action="get") + result = self._fetch( + code_mapping=DCDHTTPCodes.get, + endpoint=endpoint, + method="GET", + ) + + if result.http_code == 200: + self.state = "present" + try: + current_raw = json.loads(result.content) + extensions = current_raw.get("extensions", {}) + self.current = {} + + # Mapping fields + self.current["dcd_id"] = current_raw.get("id") + self.current["title"] = extensions.get("title") + self.current["comment"] = extensions.get("comment") + self.current["documentation_url"] = extensions.get("docu_url") + self.current["disabled"] = extensions.get("disabled") + self.current["site"] = extensions.get("site") + + # Connector and its settings + connector = extensions.get("connector", []) + if isinstance(connector, list) and len(connector) == 2: + self.current["connector_type"] = connector[0] + connector_settings = connector[1] + + # Mapping connector settings + self.current["interval"] = connector_settings.get("interval") + self.current["discover_on_creation"] = connector_settings.get( + "discover_on_creation" + ) + self.current["no_deletion_time_after_init"] = ( + connector_settings.get("no_deletion_time_after_init") + ) + self.current["max_cache_age"] = connector_settings.get( + "max_cache_age" + ) + self.current["validity_period"] = connector_settings.get( + "validity_period" + ) + self.current["restrict_source_hosts"] = connector_settings.get( + "source_filters", [] + ) + self.current["activate_changes_interval"] = connector_settings.get( + "activate_changes_interval" + ) + + # Mapping 'activation_exclude_times' to 'exclude_time_ranges' + activation_exclude_times = connector_settings.get( + "activation_exclude_times", [] + ) + self.current["exclude_time_ranges"] = [] + for time_range in activation_exclude_times: + start_time = time_range[0] + end_time = time_range[1] + self.current["exclude_time_ranges"].append( + { + "start": f"{start_time[0]:02d}:{start_time[1]:02d}", + "end": f"{end_time[0]:02d}:{end_time[1]:02d}", + } + ) + + # Mapping 'creation_rules' + self.current["creation_rules"] = [] + for rule in connector_settings.get("creation_rules", []): + # Transform 'create_folder_path' to 'folder_path' and add leading slash + folder_path = rule.get("create_folder_path", "") + if not folder_path.startswith("/"): + folder_path = "/" + folder_path + + # Transform 'host_attributes' from list of lists to dictionary + host_attributes_list = rule.get("host_attributes", []) + host_attributes_dict = {} + for attr_pair in host_attributes_list: + if isinstance(attr_pair, list) and len(attr_pair) == 2: + host_attributes_dict[attr_pair[0]] = attr_pair[1] + else: + self.module.fail_json( + msg="Unexpected structure in 'host_attributes'." + ) + + mapped_rule = { + "folder_path": folder_path, + "delete_hosts": rule.get("delete_hosts"), + "host_attributes": host_attributes_dict, + } + self.current["creation_rules"].append(mapped_rule) + else: + self.module.fail_json( + msg="Unexpected structure in 'connector' field." + ) + except json.JSONDecodeError: + self.module.fail_json( + msg="Failed to decode JSON response from API.", + content=result.content, + ) + else: + self.state = "absent" + self.current = {} + + def _build_endpoint(self, action="get"): + """ + Builds the API endpoint URL for the DCD configuration. + + Args: + action (str): The action for which to build the endpoint. Options are 'create', 'get', 'edit', 'delete'. + + Returns: + str: API endpoint URL. + """ + if action == "create": + return "/domain-types/dcd/collections/all" + elif action in ["get", "edit", "delete"]: + return f"/objects/dcd/{self.dcd_id}" + else: + self.module.fail_json( + msg=f"Unsupported action '{action}' for building endpoint." + ) + + def needs_update(self): + """ + Determines whether an update to the DCD configuration is needed. + + Returns: + bool: True if changes are needed, False otherwise. + """ + return self.differ.needs_update() + + def generate_diff(self, deletion=False): + """ + Generates a diff between the current and desired state. + + Args: + deletion (bool): Whether the diff is for a deletion. + + Returns: + dict: Dictionary containing 'before' and 'after' states. + """ + return self.differ.generate_diff(deletion) + + def _perform_action(self, action, method, data=None): + """ + Helper method to perform CRUD actions. + + Args: + action (str): The action being performed ('create', 'edit', 'delete'). + method (str): The HTTP method. + data (dict, optional): The data to send with the request. + + Returns: + dict: The result dictionary. + """ + endpoint = self._build_endpoint(action=action) + + diff = None + if self.module._diff: + deletion_flag = action == "delete" + diff = self.generate_diff(deletion=deletion_flag) + + if self.module.check_mode: + action_msgs = { + "create": "would be created", + "edit": "would be modified", + "delete": "would be deleted", + } + return dict( + msg=f"DCD configuration {action_msgs.get(action, action)}.", + changed=True, # Indicate that changes would occur + diff=diff, + ) + + response = self._fetch( + code_mapping=getattr(DCDHTTPCodes, action), + endpoint=endpoint, + data=data, + method=method, + ) + + if response.failed: + if response.http_code == 405: + self.module.fail_json( + msg=f"Method Not Allowed: Unable to {action} the DCD configuration.", + content=response.content, + ) + else: + self.module.fail_json(msg=response.msg, content=response.content) + + result_dict = { + "changed": response.changed, + "msg": response.msg, + "http_code": response.http_code, + "content": json.loads(response.content) if response.content else {}, + } + + if diff: + result_dict["diff"] = diff + + return result_dict + + def create(self): + """ + Creates a new DCD configuration via the Checkmk API. + + Returns: + dict: The result of the creation operation. + """ + filtered_data = {k: v for k, v in self.desired.items() if v is not None} + return self._perform_action(action="create", method="POST", data=filtered_data) + + def edit(self): + """ + Updates an existing DCD configuration via the Checkmk API. + + Returns: + dict: The result of the update operation. + """ + filtered_data = {k: v for k, v in self.desired.items() if v is not None} + return self._perform_action(action="edit", method="PUT", data=filtered_data) + + def delete(self): + """ + Deletes an existing DCD configuration via the Checkmk API. + + Returns: + dict: The result of the deletion operation. + """ + return self._perform_action(action="delete", method="DELETE") + + +def run_module(): + """ + The main logic for the Ansible module. + + This function defines the module parameters, initializes the DCDAPI, and performs + the appropriate action (create or delete) based on the state of the DCD configuration. + + Note: Update functionality is currently disabled due to the lack of a REST API endpoint for updates. + + Returns: + None: The result is returned to Ansible via module.exit_json(). + """ + module_args = dict( + server_url=dict(type="str", required=True), + site=dict(type="str", required=True), + auth_type=dict( + type="str", choices=["bearer", "basic", "cookie"], default="bearer" + ), + automation_user=dict(type="str"), + automation_secret=dict(type="str", no_log=True), + auth_cookie=dict(type="str", no_log=True), + dcd_config=dict( + type="dict", + required=True, + options={ + "dcd_id": dict(type="str", required=True), + "title": dict(type="str", required=False), + "comment": dict(type="str", default=""), + "site": dict(type="str", required=False), + "connector_type": dict(type="str", default="piggyback"), + "interval": dict(type="int", default=60), + "creation_rules": dict( + type="list", + elements="dict", + options={ + "folder_path": dict(type="str", required=True), + "delete_hosts": dict(type="bool", default=False), + "host_attributes": dict(type="dict", required=False), + }, + ), + "exclude_time_ranges": dict(type="list", required=False), + "discover_on_creation": dict(type="bool", default=True), + "restrict_source_hosts": dict(type="list", elements="str"), + "no_deletion_time_after_init": dict(type="int", required=False), + "max_cache_age": dict(type="int", required=False), + "validity_period": dict(type="int", required=False), + }, + ), + state=dict(type="str", default="present", choices=["present", "absent"]), + validate_certs=dict(type="bool", default=True), + ) + + required_if = [ + ("auth_type", "bearer", ["automation_user", "automation_secret"]), + ("auth_type", "basic", ["automation_user", "automation_secret"]), + ("auth_type", "cookie", ["auth_cookie"]), + ] + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=required_if, + ) + + desired_state = module.params["state"] + dcd_api = DCDAPI(module) + + try: + if desired_state == "present": + if dcd_api.state == "absent": + result = dcd_api.create() + elif dcd_api.needs_update(): + result = dict( + changed=False, + msg="DCD object cannot be updated. No REST API endpoint available for updates.", + diff=dcd_api.generate_diff(), + ) + else: + result = dict( + changed=False, + msg="DCD configuration is already in the desired state.", + ) + elif desired_state == "absent": + if dcd_api.state == "present": + result = dcd_api.delete() + else: + result = dict( + changed=False, + msg="DCD configuration is already absent.", + ) + except Exception as e: + module.fail_json(msg=f"Error managing the DCD configuration: {e}") + + module.exit_json(**result) + + +def main(): + """ + Main entry point for the module. + + This function is invoked when the module is executed directly. + + Returns: + None: Calls run_module() to handle the logic. + """ + run_module() + + +if __name__ == "__main__": + main() From 09287c8d285bd7f653e253ec3c8b1301f3a713ee Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 28 May 2025 15:29:08 +0200 Subject: [PATCH 02/16] Fix linting for BI modules. --- plugins/lookup/bi_aggregation.py | 46 ++++++++------- plugins/lookup/bi_rule.py | 36 +++++++----- plugins/modules/bi_aggregation.py | 52 ++++++++-------- plugins/modules/bi_pack.py | 38 +++++------- plugins/modules/bi_rule.py | 98 +++++++++++-------------------- 5 files changed, 116 insertions(+), 154 deletions(-) diff --git a/plugins/lookup/bi_aggregation.py b/plugins/lookup/bi_aggregation.py index 7ba0c82f0..be69f930c 100644 --- a/plugins/lookup/bi_aggregation.py +++ b/plugins/lookup/bi_aggregation.py @@ -43,19 +43,19 @@ - section: checkmk_lookup key: site - auth_type: + automation_auth_type: description: Authentication type to use with the Checkmk API. type: str required: False choices: ["bearer", "basic", "cookie"] default: "bearer" vars: - - name: ansible_lookup_checkmk_auth_type + - name: ansible_lookup_checkmk_automation_auth_type env: - - name: ANSIBLE_LOOKUP_CHECKMK_AUTH_TYPE + - name: ANSIBLE_LOOKUP_CHECKMK_automation_auth_type ini: - section: checkmk_lookup - key: auth_type + key: automation_auth_type automation_user: description: Automation user for the REST API access. @@ -79,16 +79,16 @@ - section: checkmk_lookup key: automation_secret - auth_cookie: + automation_auth_cookie: description: Authentication cookie for the REST API access. required: False vars: - - name: ansible_lookup_checkmk_auth_cookie + - name: ansible_lookup_checkmk_automation_auth_cookie env: - - name: ANSIBLE_LOOKUP_CHECKMK_AUTH_COOKIE + - name: ANSIBLE_LOOKUP_CHECKMK_automation_auth_cookie ini: - section: checkmk_lookup - key: auth_cookie + key: automation_auth_cookie validate_certs: description: Whether or not to validate TLS certificates. @@ -137,10 +137,10 @@ class ExtendedCheckmkAPI(CheckMKLookupAPI): def __init__( self, site_url, - auth_type="bearer", + automation_auth_type="bearer", automation_user=None, automation_secret=None, - auth_cookie=None, + automation_auth_cookie=None, validate_certs=True, ): headers = { @@ -150,7 +150,7 @@ def __init__( cookies = {} # Bearer Authentication: "Bearer USERNAME PASSWORD" - if auth_type == "bearer": + if automation_auth_type == "bearer": if not automation_user or not automation_secret: raise ValueError( "`automation_user` and `automation_secret` are required for bearer authentication." @@ -158,7 +158,7 @@ def __init__( headers["Authorization"] = f"Bearer {automation_user} {automation_secret}" # Basic Authentication - elif auth_type == "basic": + elif automation_auth_type == "basic": if not automation_user or not automation_secret: raise ValueError( "`automation_user` and `automation_secret` are required for basic authentication." @@ -168,13 +168,17 @@ def __init__( headers["Authorization"] = f"Basic {auth_b64}" # Cookie Authentication - elif auth_type == "cookie": - if not auth_cookie: - raise ValueError("`auth_cookie` is required for cookie authentication.") - cookies["auth_cmk"] = auth_cookie + elif automation_auth_type == "cookie": + if not automation_auth_cookie: + raise ValueError( + "`automation_auth_cookie` is required for cookie authentication." + ) + cookies["auth_cmk"] = automation_auth_cookie else: - raise ValueError(f"Unsupported `auth_type`: {auth_type}") + raise ValueError( + f"Unsupported `automation_auth_type`: {automation_auth_type}" + ) super().__init__( site_url=site_url, user="", secret="", validate_certs=validate_certs @@ -189,10 +193,10 @@ def run(self, terms, variables=None, **kwargs): aggregation_id = terms[0] server_url = self.get_option("server_url") site = self.get_option("site") - auth_type = self.get_option("auth_type") + automation_auth_type = self.get_option("automation_auth_type") automation_user = self.get_option("automation_user") automation_secret = self.get_option("automation_secret") - auth_cookie = self.get_option("auth_cookie") + automation_auth_cookie = self.get_option("automation_auth_cookie") validate_certs = self.get_option("validate_certs") site_url = f"{server_url.rstrip('/')}/{site}" @@ -200,10 +204,10 @@ def run(self, terms, variables=None, **kwargs): try: api = ExtendedCheckmkAPI( site_url=site_url, - auth_type=auth_type, + automation_auth_type=automation_auth_type, automation_user=automation_user, automation_secret=automation_secret, - auth_cookie=auth_cookie, + automation_auth_cookie=automation_auth_cookie, validate_certs=validate_certs, ) except ValueError as e: diff --git a/plugins/lookup/bi_rule.py b/plugins/lookup/bi_rule.py index 217bf8fb1..1b8cd2be1 100644 --- a/plugins/lookup/bi_rule.py +++ b/plugins/lookup/bi_rule.py @@ -43,7 +43,7 @@ - section: checkmk_lookup key: site - auth_type: + automation_auth_type: description: The authentication type to use ('bearer', 'basic', 'cookie'). required: False default: 'bearer' @@ -56,7 +56,7 @@ description: The automation secret or password for authentication. required: True - auth_cookie: + automation_auth_cookie: description: The authentication cookie value if using cookie-based authentication. required: False @@ -128,10 +128,10 @@ class ExtendedCheckmkAPI(CheckMKLookupAPI): def __init__( self, site_url, - auth_type="bearer", + automation_auth_type="bearer", automation_user=None, automation_secret=None, - auth_cookie=None, + automation_auth_cookie=None, validate_certs=True, ): headers = { @@ -141,7 +141,7 @@ def __init__( cookies = {} # Bearer Authentication: "Bearer USERNAME PASSWORD" - if auth_type == "bearer": + if automation_auth_type == "bearer": if not automation_user or not automation_secret: raise ValueError( "`automation_user` and `automation_secret` are required for bearer authentication." @@ -149,7 +149,7 @@ def __init__( headers["Authorization"] = f"Bearer {automation_user} {automation_secret}" # Basic Authentication - elif auth_type == "basic": + elif automation_auth_type == "basic": if not automation_user or not automation_secret: raise ValueError( "`automation_user` and `automation_secret` are required for basic authentication." @@ -159,13 +159,17 @@ def __init__( headers["Authorization"] = f"Basic {auth_b64}" # Cookie Authentication - elif auth_type == "cookie": - if not auth_cookie: - raise ValueError("`auth_cookie` is required for cookie authentication.") - cookies["auth_cmk"] = auth_cookie + elif automation_auth_type == "cookie": + if not automation_auth_cookie: + raise ValueError( + "`automation_auth_cookie` is required for cookie authentication." + ) + cookies["auth_cmk"] = automation_auth_cookie else: - raise ValueError(f"Unsupported `auth_type`: {auth_type}") + raise ValueError( + f"Unsupported `automation_auth_type`: {automation_auth_type}" + ) super().__init__( site_url=site_url, user="", secret="", validate_certs=validate_certs @@ -182,10 +186,10 @@ def run(self, terms, variables=None, **kwargs): try: server_url = self.get_option("server_url") site = self.get_option("site") - auth_type = self.get_option("auth_type") or "bearer" + automation_auth_type = self.get_option("automation_auth_type") or "bearer" automation_user = self.get_option("automation_user") automation_secret = self.get_option("automation_secret") - auth_cookie = self.get_option("auth_cookie") + automation_auth_cookie = self.get_option("automation_auth_cookie") validate_certs = self.get_option("validate_certs") except KeyError as e: raise AnsibleError(f"Missing required configuration option: {str(e)}") @@ -193,17 +197,17 @@ def run(self, terms, variables=None, **kwargs): site_url = f"{server_url.rstrip('/')}/{site}" # Optional: Debugging prints - # print(f"auth_type: {auth_type}") + # print(f"automation_auth_type: {automation_auth_type}") # print(f"server_url: {server_url}") # print(f"rule_id: {rule_id}") try: api = ExtendedCheckmkAPI( site_url=site_url, - auth_type=auth_type, + automation_auth_type=automation_auth_type, automation_user=automation_user, automation_secret=automation_secret, - auth_cookie=auth_cookie, + automation_auth_cookie=automation_auth_cookie, validate_certs=validate_certs, ) except ValueError as e: diff --git a/plugins/modules/bi_aggregation.py b/plugins/modules/bi_aggregation.py index 6c9aa557a..8f47a5e5e 100644 --- a/plugins/modules/bi_aggregation.py +++ b/plugins/modules/bi_aggregation.py @@ -69,7 +69,7 @@ description: - Node generation definition. type: dict - required: true + required: false suboptions: search: description: @@ -107,7 +107,7 @@ checkmk.general.bi_aggregation: server_url: "http://myserver/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" aggregation: @@ -142,7 +142,7 @@ checkmk.general.bi_aggregation: server_url: "http://myserver/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" aggregation: @@ -174,6 +174,9 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer +from ansible_collections.checkmk.general.plugins.module_utils.utils import ( + base_argument_spec, +) class ExtendedCheckmkAPI(CheckmkAPI): @@ -185,12 +188,12 @@ class ExtendedCheckmkAPI(CheckmkAPI): def __init__(self, module): """Initialize ExtendedCheckmkAPI with authentication handling.""" super().__init__(module) - auth_type = self.params.get("auth_type", "bearer") + automation_auth_type = self.params.get("automation_auth_type", "bearer") automation_user = self.params.get("automation_user") automation_secret = self.params.get("automation_secret") - auth_cookie = self.params.get("auth_cookie") + automation_auth_cookie = self.params.get("automation_auth_cookie") - if auth_type == "bearer": + if automation_auth_type == "bearer": if not automation_user or not automation_secret: self.module.fail_json( msg="`automation_user` and `automation_secret` are required for bearer authentication." @@ -198,7 +201,7 @@ def __init__(self, module): self.headers["Authorization"] = ( f"Bearer {automation_user} {automation_secret}" ) - elif auth_type == "basic": + elif automation_auth_type == "basic": if not automation_user or not automation_secret: self.module.fail_json( msg="`automation_user` and `automation_secret` are required for basic authentication." @@ -206,14 +209,16 @@ def __init__(self, module): auth_str = f"{automation_user}:{automation_secret}" auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") self.headers["Authorization"] = f"Basic {auth_b64}" - elif auth_type == "cookie": - if not auth_cookie: + elif automation_auth_type == "cookie": + if not automation_auth_cookie: self.module.fail_json( - msg="`auth_cookie` is required for cookie authentication." + msg="`automation_auth_cookie` is required for cookie authentication." ) - self.cookies["auth_cmk"] = auth_cookie + self.cookies["auth_cmk"] = automation_auth_cookie else: - self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") + self.module.fail_json( + msg=f"Unsupported `automation_auth_type`: {automation_auth_type}" + ) class BIAggregationHTTPCodes: @@ -415,18 +420,8 @@ def run_module(): Returns: None: The result is returned to Ansible via module.exit_json(). """ - module_args = dict( - server_url=dict(type="str", required=True), - site=dict(type="str", required=True), - auth_type=dict( - type="str", - choices=["bearer", "basic", "cookie"], - default="bearer", - required=False, - ), - automation_user=dict(type="str", required=False), - automation_secret=dict(type="str", required=False, no_log=True), - auth_cookie=dict(type="str", required=False, no_log=True), + argument_spec = base_argument_spec() + argument_spec.update( aggregation=dict( type="dict", required=True, @@ -466,17 +461,16 @@ def run_module(): ), ), state=dict(type="str", default="present", choices=["present", "absent"]), - validate_certs=dict(type="bool", default=True, required=False), ) required_if = [ - ("auth_type", "bearer", ["automation_user", "automation_secret"]), - ("auth_type", "basic", ["automation_user", "automation_secret"]), - ("auth_type", "cookie", ["auth_cookie"]), + ("automation_auth_type", "bearer", ["automation_user", "automation_secret"]), + ("automation_auth_type", "basic", ["automation_user", "automation_secret"]), + ("automation_auth_type", "cookie", ["automation_auth_cookie"]), ] module = AnsibleModule( - argument_spec=module_args, + argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, ) diff --git a/plugins/modules/bi_pack.py b/plugins/modules/bi_pack.py index 083abddc2..46cd45054 100644 --- a/plugins/modules/bi_pack.py +++ b/plugins/modules/bi_pack.py @@ -35,7 +35,7 @@ title: description: Title of the BI pack. type: str - required: true + required: false contact_groups: description: List of contact groups associated with the BI pack. @@ -70,7 +70,7 @@ bi_pack: server_url: "https://checkmk.example.com/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" pack: @@ -86,7 +86,7 @@ bi_pack: server_url: "https://checkmk.example.com/" site: "mysite" - auth_type: "basic" + automation_auth_type: "basic" automation_user: "basicuser" automation_secret: "basicpassword" pack: @@ -101,8 +101,8 @@ bi_pack: server_url: "https://checkmk.example.com/" site: "mysite" - auth_type: "cookie" - auth_cookie: "sessionid=abc123xyz" + automation_auth_type: "cookie" + automation_auth_cookie: "sessionid=abc123xyz" pack: id: "storage_pack" title: "Storage Pack" @@ -115,7 +115,7 @@ bi_pack: server_url: "https://checkmk.example.com/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" pack: @@ -147,6 +147,9 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer +from ansible_collections.checkmk.general.plugins.module_utils.utils import ( + base_argument_spec, +) class BIPackHTTPCodes: @@ -360,18 +363,8 @@ def run_module(): Returns: None: The result is returned to Ansible via module.exit_json(). """ - module_args = dict( - server_url=dict(type="str", required=True), - site=dict(type="str", required=True), - auth_type=dict( - type="str", - choices=["bearer", "basic", "cookie"], - default="bearer", - required=False, - ), - automation_user=dict(type="str", required=False), - automation_secret=dict(type="str", required=False, no_log=True), - auth_cookie=dict(type="str", required=False, no_log=True), + argument_spec = base_argument_spec() + argument_spec.update( pack=dict( type="dict", required=True, @@ -385,17 +378,16 @@ def run_module(): ), ), state=dict(type="str", default="present", choices=["present", "absent"]), - validate_certs=dict(type="bool", default=True, required=False), ) required_if = [ - ("auth_type", "bearer", ["automation_user", "automation_secret"]), - ("auth_type", "basic", ["automation_user", "automation_secret"]), - ("auth_type", "cookie", ["auth_cookie"]), + ("automation_auth_type", "bearer", ["automation_user", "automation_secret"]), + ("automation_auth_type", "basic", ["automation_user", "automation_secret"]), + ("automation_auth_type", "cookie", ["automation_auth_cookie"]), ] module = AnsibleModule( - argument_spec=module_args, + argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, ) diff --git a/plugins/modules/bi_rule.py b/plugins/modules/bi_rule.py index 540d1c396..ff268fb81 100644 --- a/plugins/modules/bi_rule.py +++ b/plugins/modules/bi_rule.py @@ -11,12 +11,12 @@ --- module: bi_rule -short_description: Manage BI rules in Checkmk. +short_description: Manage BI rules. version_added: "6.1.0" description: - - Manage BI rules in Checkmk, including creation, updating, and deletion. + - Manage BI rules, including creation, updating, and deletion. extends_documentation_fragment: [checkmk.general.common] @@ -60,11 +60,11 @@ required: false description: Additional parameters for the BI rule. suboptions: - arguments: - type: list - elements: str - required: false - description: List of arguments for the BI rule. + arguments: + type: list + elements: str + required: false + description: List of arguments for the BI rule. state: type: str @@ -81,7 +81,7 @@ checkmk.general.bi_rule: server_url: "https://example.com/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" rule: @@ -116,7 +116,7 @@ checkmk.general.bi_rule: server_url: "https://example.com/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" rule: @@ -153,6 +153,9 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer +from ansible_collections.checkmk.general.plugins.module_utils.utils import ( + base_argument_spec, +) class ExtendedCheckmkAPI(CheckmkAPI): @@ -164,12 +167,12 @@ class ExtendedCheckmkAPI(CheckmkAPI): def __init__(self, module): """Initialize ExtendedCheckmkAPI with authentication handling.""" super().__init__(module) - auth_type = self.params.get("auth_type", "bearer") + automation_auth_type = self.params.get("automation_auth_type", "bearer") automation_user = self.params.get("automation_user") automation_secret = self.params.get("automation_secret") - auth_cookie = self.params.get("auth_cookie") + automation_auth_cookie = self.params.get("automation_auth_cookie") - if auth_type == "bearer": + if automation_auth_type == "bearer": if not automation_user or not automation_secret: self.module.fail_json( msg="`automation_user` and `automation_secret` are required for bearer authentication." @@ -177,7 +180,7 @@ def __init__(self, module): self.headers["Authorization"] = ( f"Bearer {automation_user} {automation_secret}" ) - elif auth_type == "basic": + elif automation_auth_type == "basic": if not automation_user or not automation_secret: self.module.fail_json( msg="`automation_user` and `automation_secret` are required for basic authentication." @@ -185,14 +188,16 @@ def __init__(self, module): auth_str = f"{automation_user}:{automation_secret}" auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") self.headers["Authorization"] = f"Basic {auth_b64}" - elif auth_type == "cookie": - if not auth_cookie: + elif automation_auth_type == "cookie": + if not automation_auth_cookie: self.module.fail_json( - msg="`auth_cookie` is required for cookie authentication." + msg="`automation_auth_cookie` is required for cookie authentication." ) - self.cookies["auth_cmk"] = auth_cookie + self.cookies["auth_cmk"] = automation_auth_cookie else: - self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") + self.module.fail_json( + msg=f"Unsupported `automation_auth_type`: {automation_auth_type}" + ) class BIRuleHTTPCodes: @@ -394,79 +399,49 @@ def run_module(): Returns: None: The result is returned to Ansible via module.exit_json(). """ - module_args = dict( - server_url=dict(type="str", required=True), - site=dict(type="str", required=True), - auth_type=dict( - type="str", - choices=["bearer", "basic", "cookie"], - default="bearer", - required=False, - ), - automation_user=dict( - type="str", - required=False, - description="The automation user for API authentication.", - ), - automation_secret=dict( - type="str", - required=False, - no_log=True, - description="The automation secret (password) for API authentication.", - ), - auth_cookie=dict( - type="str", - required=False, - no_log=True, - description="The authentication cookie for API authentication.", - ), + argument_spec = base_argument_spec() + argument_spec.update( rule=dict( type="dict", required=True, - description="Definition of the BI rule as needed by the Checkmk API.", options=dict( pack_id=dict( type="str", required=True, - description="The identifier of the BI pack.", ), id=dict( - type="str", required=True, description="The unique BI rule ID." + type="str", + required=True, ), nodes=dict( type="list", elements="dict", required=True, - description="List of nodes associated with the BI rule.", ), properties=dict( - type="dict", required=True, description="Properties of the BI rule." + type="dict", + required=True, ), aggregation_function=dict( type="dict", required=True, - description="Aggregation function configuration.", ), computation_options=dict( type="dict", required=True, - description="Computation options for the BI rule.", ), node_visualization=dict( type="dict", required=True, - description="Visualization options for the BI rule nodes.", ), params=dict( type="dict", required=False, - description="Additional parameters for the BI rule.", options=dict( arguments=dict( type="list", elements="str", required=False, - description="List of arguments for the BI rule.", ), ), ), @@ -476,24 +451,17 @@ def run_module(): type="str", default="present", choices=["present", "absent"], - description="State of the BI rule.", - ), - validate_certs=dict( - type="bool", - default=True, - required=False, - description="Whether to validate SSL certificates.", ), ) required_if = [ - ("auth_type", "bearer", ["automation_user", "automation_secret"]), - ("auth_type", "basic", ["automation_user", "automation_secret"]), - ("auth_type", "cookie", ["auth_cookie"]), + ("automation_auth_type", "bearer", ["automation_user", "automation_secret"]), + ("automation_auth_type", "basic", ["automation_user", "automation_secret"]), + ("automation_auth_type", "cookie", ["automation_auth_cookie"]), ] module = AnsibleModule( - argument_spec=module_args, + argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, ) From b7ba89c0bdacdc62f8f919726e49511e802b7074 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 28 May 2025 17:55:05 +0200 Subject: [PATCH 03/16] Update meta data. --- .github/labels-issues.yml | 18 ++++++++++++++++++ .github/labels-prs.yml | 30 ++++++++++++++++++++++++++++++ meta/runtime.yml | 4 ++++ 3 files changed, 52 insertions(+) diff --git a/.github/labels-issues.yml b/.github/labels-issues.yml index b1517633a..40760fc68 100644 --- a/.github/labels-issues.yml +++ b/.github/labels-issues.yml @@ -17,6 +17,15 @@ inventory: module:activation: - 'Component Name: activation' +module:bi_aggregation: + - 'Component Name: bi_aggregation' + +module:bi_pack: + - 'Component Name: bi_pack' + +module:bi_rule: + - 'Component Name: bi_rule' + module:bakery: - 'Component Name: bakery' @@ -62,6 +71,15 @@ module:user: lookup:bakery: - 'Component Name: lookup_bakery' +lookup:bi_aggregation: + - 'Component Name: lookup_bi_aggregation' + +lookup:bi_pack: + - 'Component Name: lookup_bi_pack' + +lookup:bi_rule: + - 'Component Name: lookup_bi_rule' + lookup:folder: - 'Component Name: lookup_folder' diff --git a/.github/labels-prs.yml b/.github/labels-prs.yml index 3dc75767c..0814256ab 100644 --- a/.github/labels-prs.yml +++ b/.github/labels-prs.yml @@ -24,6 +24,21 @@ module:activation: - changed-files: - any-glob-to-any-file: 'plugins/modules/activation.py' +module:bi_aggregation: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/bi_aggregation.py' + +module:bi_pack: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/bi_pack.py' + +module:bi_rule: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/bi_rule.py' + module:bakery: - any: - changed-files: @@ -99,6 +114,21 @@ lookup:bakery: - changed-files: - any-glob-to-any-file: 'plugins/modules/lookup/bakery.py' +lookup:bi_aggregation: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/lookup/bi_aggregation.py' + +lookup:bi_pack: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/lookup/bi_pack.py' + +lookup:bi_rule: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/lookup/bi_rule.py' + lookup:folder: - any: - changed-files: diff --git a/meta/runtime.yml b/meta/runtime.yml index 0b7e1be5a..317ca8085 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -4,7 +4,11 @@ action_groups: checkmk: - activation - bakery + - bi_aggregation + - bi_pack + - bi_rule - contact_group + - dcd - discovery - downtime - folder From ff5572ca89934da89f551cd19c14eccd5ab884bc Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 28 May 2025 17:55:32 +0200 Subject: [PATCH 04/16] Add very basic integration tests for BI lookup modules. --- .../ans-int-test-lkp-bi_aggregation.yaml | 121 ++++++++++++++++++ .../workflows/ans-int-test-lkp-bi_pack.yaml | 121 ++++++++++++++++++ .../workflows/ans-int-test-lkp-bi_rule.yaml | 121 ++++++++++++++++++ plugins/lookup/bi_aggregation.py | 28 +++- plugins/lookup/bi_pack.py | 4 +- .../lookup_bi_aggregation/tasks/main.yml | 35 +++++ .../lookup_bi_aggregation/tasks/test.yml | 46 +++++++ .../lookup_bi_aggregation/vars/main.yml | 22 ++++ .../targets/lookup_bi_pack/tasks/main.yml | 35 +++++ .../targets/lookup_bi_pack/tasks/test.yml | 46 +++++++ .../targets/lookup_bi_pack/vars/main.yml | 22 ++++ .../targets/lookup_bi_rule/tasks/main.yml | 35 +++++ .../targets/lookup_bi_rule/tasks/test.yml | 46 +++++++ .../targets/lookup_bi_rule/vars/main.yml | 22 ++++ 14 files changed, 701 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ans-int-test-lkp-bi_aggregation.yaml create mode 100644 .github/workflows/ans-int-test-lkp-bi_pack.yaml create mode 100644 .github/workflows/ans-int-test-lkp-bi_rule.yaml create mode 100644 tests/integration/targets/lookup_bi_aggregation/tasks/main.yml create mode 100644 tests/integration/targets/lookup_bi_aggregation/tasks/test.yml create mode 100644 tests/integration/targets/lookup_bi_aggregation/vars/main.yml create mode 100644 tests/integration/targets/lookup_bi_pack/tasks/main.yml create mode 100644 tests/integration/targets/lookup_bi_pack/tasks/test.yml create mode 100644 tests/integration/targets/lookup_bi_pack/vars/main.yml create mode 100644 tests/integration/targets/lookup_bi_rule/tasks/main.yml create mode 100644 tests/integration/targets/lookup_bi_rule/tasks/test.yml create mode 100644 tests/integration/targets/lookup_bi_rule/vars/main.yml diff --git a/.github/workflows/ans-int-test-lkp-bi_aggregation.yaml b/.github/workflows/ans-int-test-lkp-bi_aggregation.yaml new file mode 100644 index 000000000..43790bfcb --- /dev/null +++ b/.github/workflows/ans-int-test-lkp-bi_aggregation.yaml @@ -0,0 +1,121 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: lookup_bi_aggregation + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for the BI Aggregation Lookup Module +on: + workflow_dispatch: + pull_request: + branches: + - main + - devel + paths: + - 'plugins/lookup/bi_aggregation.py' + push: + paths: + - 'plugins/lookup/bi_aggregation.py' + - '.github/workflows/ans-int-test-lkp-bi_aggregation.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Provide secrets file + run: echo "${{ secrets.CHECKMK_DOWNLOAD_PW }}" > ./tests/integration/files/.dl-secret + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + env: + CHECKMK_DOWNLOAD_PW: ${{ secrets.CHECKMK_DOWNLOAD_PW }} + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/.github/workflows/ans-int-test-lkp-bi_pack.yaml b/.github/workflows/ans-int-test-lkp-bi_pack.yaml new file mode 100644 index 000000000..4a6da5094 --- /dev/null +++ b/.github/workflows/ans-int-test-lkp-bi_pack.yaml @@ -0,0 +1,121 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: lookup_bi_pack + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for the BI Pack Lookup Module +on: + workflow_dispatch: + pull_request: + branches: + - main + - devel + paths: + - 'plugins/lookup/bi_pack.py' + push: + paths: + - 'plugins/lookup/bi_pack.py' + - '.github/workflows/ans-int-test-lkp-bi_pack.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Provide secrets file + run: echo "${{ secrets.CHECKMK_DOWNLOAD_PW }}" > ./tests/integration/files/.dl-secret + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + env: + CHECKMK_DOWNLOAD_PW: ${{ secrets.CHECKMK_DOWNLOAD_PW }} + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/.github/workflows/ans-int-test-lkp-bi_rule.yaml b/.github/workflows/ans-int-test-lkp-bi_rule.yaml new file mode 100644 index 000000000..0f2c98f7e --- /dev/null +++ b/.github/workflows/ans-int-test-lkp-bi_rule.yaml @@ -0,0 +1,121 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: lookup_bi_rule + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for the BI Rule Lookup Module +on: + workflow_dispatch: + pull_request: + branches: + - main + - devel + paths: + - 'plugins/lookup/bi_rule.py' + push: + paths: + - 'plugins/lookup/bi_rule.py' + - '.github/workflows/ans-int-test-lkp-bi_rule.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Provide secrets file + run: echo "${{ secrets.CHECKMK_DOWNLOAD_PW }}" > ./tests/integration/files/.dl-secret + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + env: + CHECKMK_DOWNLOAD_PW: ${{ secrets.CHECKMK_DOWNLOAD_PW }} + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/plugins/lookup/bi_aggregation.py b/plugins/lookup/bi_aggregation.py index be69f930c..2b4d570e8 100644 --- a/plugins/lookup/bi_aggregation.py +++ b/plugins/lookup/bi_aggregation.py @@ -108,7 +108,33 @@ - The directory of the play is used as the current working directory. """ -# TODO Add examples here and check return +EXAMPLES = """ +- name: "Get the default BI aggregation." + ansible.builtin.debug: + msg: "BI aggregation: {{ bi_aggregation_details }}" + vars: + bi_aggregation_details: "{{ + lookup('checkmk.general.bi_aggregation', + 'default_aggregation', + server_url='http://myserver/', + site='mysite', + automation_user='myuser', + automation_secret='mysecret', + validate_certs=False + ) + }}" + +- name: "Use variables outside the module call." + ansible.builtin.debug: + msg: "BI bi_aggregation: {{ bi_aggregation_details }}" + vars: + ansible_lookup_checkmk_server_url: "http://myserver/" + ansible_lookup_checkmk_site: "mysite" + ansible_lookup_checkmk_automation_user: "myuser" + ansible_lookup_checkmk_automation_secret: "mysecret" + ansible_lookup_checkmk_validate_certs: false + bi_aggregation_details: "{{ lookup('checkmk.general.bi_aggregation', 'default_aggregation') }}" +""" RETURN = """ _list: diff --git a/plugins/lookup/bi_pack.py b/plugins/lookup/bi_pack.py index 5d260f133..9093cb09f 100644 --- a/plugins/lookup/bi_pack.py +++ b/plugins/lookup/bi_pack.py @@ -93,7 +93,7 @@ vars: attributes: "{{ lookup('checkmk.general.bi_pack', - 'example_pack', + 'default', server_url=my_server_url, site=mysite, automation_user=myuser, @@ -111,7 +111,7 @@ ansible_lookup_checkmk_automation_user: "myuser" ansible_lookup_checkmk_automation_secret: "mysecret" ansible_lookup_checkmk_validate_certs: false - attributes: "{{ lookup('checkmk.general.bi_pack', 'example_pack') }}" + attributes: "{{ lookup('checkmk.general.bi_pack', 'default') }}" """ RETURN = """ diff --git a/tests/integration/targets/lookup_bi_aggregation/tasks/main.yml b/tests/integration/targets/lookup_bi_aggregation/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/lookup_bi_aggregation/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/lookup_bi_aggregation/tasks/test.yml b/tests/integration/targets/lookup_bi_aggregation/tasks/test.yml new file mode 100644 index 000000000..234c69c4a --- /dev/null +++ b/tests/integration/targets/lookup_bi_aggregation/tasks/test.yml @@ -0,0 +1,46 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: outer_item.edition == "cme" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: outer_item.edition != "cme" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Get BI aggregation 'default_aggregation'." + ansible.builtin.debug: + var: __checkmk_var_bi_aggregation + vars: + __checkmk_var_bi_aggregation: "{{ lookup('checkmk.general.bi_aggregation', + 'default_aggregation', + server_url=checkmk_var_server_url, + site=outer_item.site, + validate_certs=False, + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) + }}" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Verify BI aggregation pack ID." + ansible.builtin.assert: + that: "__checkmk_var_bi_aggregation.pack_id == 'default'" + vars: + __checkmk_var_bi_aggregation: "{{ lookup('checkmk.general.bi_aggregation', + 'default_aggregation', + server_url=checkmk_var_server_url, + site=outer_item.site, + validate_certs=False, + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) + }}" + delegate_to: localhost + +# These do not work currently, but the issue is independent and affects other modules as well. So we disable them only for now. +# - name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Use variables outside the module call." +# ansible.builtin.assert: +# that: "__checkmk_var_bi_aggregation.pack_id == 'default'" +# vars: # Variables are taken from `tests/integration/files/includes/vars/global.yml` +# __checkmk_var_bi_aggregation: "{{ lookup('checkmk.general.bi_aggregation', 'default_aggregation')" +# delegate_to: localhost diff --git a/tests/integration/targets/lookup_bi_aggregation/vars/main.yml b/tests/integration/targets/lookup_bi_aggregation/vars/main.yml new file mode 100644 index 000000000..18407f825 --- /dev/null +++ b/tests/integration/targets/lookup_bi_aggregation/vars/main.yml @@ -0,0 +1,22 @@ +--- +checkmk_var_test_sites: + - version: "2.2.0p42" + edition: "cre" + site: "ancient_cre" + port: "5022" + - version: "2.3.0p33" + edition: "cre" + site: "old_cre" + port: "5023" + - version: "2.3.0p33" + edition: "cme" + site: "old_cme" + port: "5323" + - version: "2.4.0p2" + edition: "cre" + site: "stable_cre" + port: "5024" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" diff --git a/tests/integration/targets/lookup_bi_pack/tasks/main.yml b/tests/integration/targets/lookup_bi_pack/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/lookup_bi_pack/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/lookup_bi_pack/tasks/test.yml b/tests/integration/targets/lookup_bi_pack/tasks/test.yml new file mode 100644 index 000000000..500ce2a93 --- /dev/null +++ b/tests/integration/targets/lookup_bi_pack/tasks/test.yml @@ -0,0 +1,46 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: outer_item.edition == "cme" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: outer_item.edition != "cme" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Get BI pack 'default'." + ansible.builtin.debug: + var: __checkmk_var_bi_pack + vars: + __checkmk_var_bi_pack: "{{ lookup('checkmk.general.bi_pack', + 'default', + server_url=checkmk_var_server_url, + site=outer_item.site, + validate_certs=False, + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) + }}" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Verify BI pack title." + ansible.builtin.assert: + that: "__checkmk_var_bi_pack.extensions.title == 'Default Pack'" + vars: + __checkmk_var_bi_pack: "{{ lookup('checkmk.general.bi_pack', + 'default', + server_url=checkmk_var_server_url, + site=outer_item.site, + validate_certs=False, + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) + }}" + delegate_to: localhost + +# These do not work currently, but the issue is independent and affects other modules as well. So we disable them only for now. +# - name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Use variables outside the module call." +# ansible.builtin.assert: +# that: "__checkmk_var_bi_pack.extensions.title == 'Default Pack'" +# vars: # Variables are taken from `tests/integration/files/includes/vars/global.yml` +# __checkmk_var_bi_pack: "{{ lookup('checkmk.general.bi_pack', 'default')" +# delegate_to: localhost diff --git a/tests/integration/targets/lookup_bi_pack/vars/main.yml b/tests/integration/targets/lookup_bi_pack/vars/main.yml new file mode 100644 index 000000000..18407f825 --- /dev/null +++ b/tests/integration/targets/lookup_bi_pack/vars/main.yml @@ -0,0 +1,22 @@ +--- +checkmk_var_test_sites: + - version: "2.2.0p42" + edition: "cre" + site: "ancient_cre" + port: "5022" + - version: "2.3.0p33" + edition: "cre" + site: "old_cre" + port: "5023" + - version: "2.3.0p33" + edition: "cme" + site: "old_cme" + port: "5323" + - version: "2.4.0p2" + edition: "cre" + site: "stable_cre" + port: "5024" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" diff --git a/tests/integration/targets/lookup_bi_rule/tasks/main.yml b/tests/integration/targets/lookup_bi_rule/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/lookup_bi_rule/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/lookup_bi_rule/tasks/test.yml b/tests/integration/targets/lookup_bi_rule/tasks/test.yml new file mode 100644 index 000000000..5709b7192 --- /dev/null +++ b/tests/integration/targets/lookup_bi_rule/tasks/test.yml @@ -0,0 +1,46 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: outer_item.edition == "cme" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: outer_item.edition != "cme" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Get BI rule 'host'." + ansible.builtin.debug: + var: __checkmk_var_bi_rule + vars: + __checkmk_var_bi_rule: "{{ lookup('checkmk.general.bi_rule', + 'host', + server_url=checkmk_var_server_url, + site=outer_item.site, + validate_certs=False, + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) + }}" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Verify BI rule title." + ansible.builtin.assert: + that: "__checkmk_var_bi_rule.properties.title == 'Host $HOSTNAME$'" + vars: + __checkmk_var_bi_rule: "{{ lookup('checkmk.general.bi_rule', + 'host', + server_url=checkmk_var_server_url, + site=outer_item.site, + validate_certs=False, + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) + }}" + delegate_to: localhost + +# These do not work currently, but the issue is independent and affects other modules as well. So we disable them only for now. +# - name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Use variables outside the module call." +# ansible.builtin.assert: +# that: "__checkmk_var_bi_rule.properties.title == 'Host $HOSTNAME$'" +# vars: # Variables are taken from `tests/integration/files/includes/vars/global.yml` +# __checkmk_var_bi_rule: "{{ lookup('checkmk.general.bi_rule', 'host')" +# delegate_to: localhost diff --git a/tests/integration/targets/lookup_bi_rule/vars/main.yml b/tests/integration/targets/lookup_bi_rule/vars/main.yml new file mode 100644 index 000000000..18407f825 --- /dev/null +++ b/tests/integration/targets/lookup_bi_rule/vars/main.yml @@ -0,0 +1,22 @@ +--- +checkmk_var_test_sites: + - version: "2.2.0p42" + edition: "cre" + site: "ancient_cre" + port: "5022" + - version: "2.3.0p33" + edition: "cre" + site: "old_cre" + port: "5023" + - version: "2.3.0p33" + edition: "cme" + site: "old_cme" + port: "5323" + - version: "2.4.0p2" + edition: "cre" + site: "stable_cre" + port: "5024" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" From 8ebab00c93e3d07b9d403fbb951703a3eee65841 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 09:55:23 +0200 Subject: [PATCH 05/16] Integrate extended_api.py into api.py. --- plugins/module_utils/api.py | 45 +++++++++++++++++++++ plugins/module_utils/extended_api.py | 59 ---------------------------- plugins/modules/bi_aggregation.py | 5 ++- plugins/modules/bi_rule.py | 5 ++- 4 files changed, 53 insertions(+), 61 deletions(-) delete mode 100644 plugins/module_utils/extended_api.py diff --git a/plugins/module_utils/api.py b/plugins/module_utils/api.py index 0c521265d..d75c03a1b 100644 --- a/plugins/module_utils/api.py +++ b/plugins/module_utils/api.py @@ -10,6 +10,7 @@ __metaclass__ = type +import base64 import json from ansible.module_utils.urls import fetch_url @@ -133,3 +134,47 @@ def getversion(self): content = result.content checkmkinfo = json.loads(content) return CheckmkVersion(checkmkinfo.get("versions").get("checkmk")) + + +class ExtendedCheckmkAPI(CheckmkAPI): + """ + ExtendedCheckmkAPI adds support for multiple authentication methods: bearer, basic, and cookie. + """ + + def __init__(self, module): + super().__init__(module) + auth_type = self.params.get("auth_type", "bearer") + automation_user = self.params.get("automation_user") + automation_secret = self.params.get("automation_secret") + auth_cookie = self.params.get("auth_cookie") + + # Bearer Authentication + if auth_type == "bearer": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for bearer authentication." + ) + self.headers["Authorization"] = ( + f"Bearer {automation_user} {automation_secret}" + ) + + # Basic Authentication + elif auth_type == "basic": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + self.headers["Authorization"] = f"Basic {auth_b64}" + + # Cookie Authentication + elif auth_type == "cookie": + if not auth_cookie: + self.module.fail_json( + msg="`auth_cookie` is required for cookie authentication." + ) + self.cookies["auth_cmk"] = auth_cookie + + else: + self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") diff --git a/plugins/module_utils/extended_api.py b/plugins/module_utils/extended_api.py deleted file mode 100644 index 19da7fbd0..000000000 --- a/plugins/module_utils/extended_api.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8; py-indent-offset: 4 -*- - -# Copyright: (c) 2025, Robin Gierse -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Ensure compatibility to Python2 -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -import base64 - -from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI - - -class ExtendedCheckmkAPI(CheckmkAPI): - """ - ExtendedCheckmkAPI adds support for multiple authentication methods: bearer, basic, and cookie. - """ - - def __init__(self, module): - super().__init__(module) - auth_type = self.params.get("auth_type", "bearer") - automation_user = self.params.get("automation_user") - automation_secret = self.params.get("automation_secret") - auth_cookie = self.params.get("auth_cookie") - - # Bearer Authentication - if auth_type == "bearer": - if not automation_user or not automation_secret: - self.module.fail_json( - msg="`automation_user` and `automation_secret` are required for bearer authentication." - ) - self.headers["Authorization"] = ( - f"Bearer {automation_user} {automation_secret}" - ) - - # Basic Authentication - elif auth_type == "basic": - if not automation_user or not automation_secret: - self.module.fail_json( - msg="`automation_user` and `automation_secret` are required for basic authentication." - ) - auth_str = f"{automation_user}:{automation_secret}" - auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") - self.headers["Authorization"] = f"Basic {auth_b64}" - - # Cookie Authentication - elif auth_type == "cookie": - if not auth_cookie: - self.module.fail_json( - msg="`auth_cookie` is required for cookie authentication." - ) - self.cookies["auth_cmk"] = auth_cookie - - else: - self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") diff --git a/plugins/modules/bi_aggregation.py b/plugins/modules/bi_aggregation.py index 8f47a5e5e..fd9d6924d 100644 --- a/plugins/modules/bi_aggregation.py +++ b/plugins/modules/bi_aggregation.py @@ -172,7 +172,10 @@ import json from ansible.module_utils.basic import AnsibleModule -from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.api import ( + CheckmkAPI, + ExtendedCheckmkAPI, +) from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer from ansible_collections.checkmk.general.plugins.module_utils.utils import ( base_argument_spec, diff --git a/plugins/modules/bi_rule.py b/plugins/modules/bi_rule.py index ff268fb81..741ad77f5 100644 --- a/plugins/modules/bi_rule.py +++ b/plugins/modules/bi_rule.py @@ -151,7 +151,10 @@ import json from ansible.module_utils.basic import AnsibleModule -from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.api import ( + CheckmkAPI, + ExtendedCheckmkAPI, +) from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer from ansible_collections.checkmk.general.plugins.module_utils.utils import ( base_argument_spec, From b0f4fcd4ed50b917605d58351d50b854e7f19dc6 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 09:57:58 +0200 Subject: [PATCH 06/16] Document automation_auth_type and automation_auth_cookie. --- plugins/doc_fragments/common.py | 4 ++-- plugins/module_utils/utils.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/doc_fragments/common.py b/plugins/doc_fragments/common.py index 44c02a275..bbb6e9f72 100644 --- a/plugins/doc_fragments/common.py +++ b/plugins/doc_fragments/common.py @@ -22,7 +22,7 @@ class ModuleDocFragment(object): description: The secret to authenticate your automation user. required: true type: str - auth_type: + automation_auth_type: description: Type of authentication to use. required: false type: str @@ -31,7 +31,7 @@ class ModuleDocFragment(object): - basic - cookie default: bearer - auth_cookie: + automation_auth_cookie: description: Authentication cookie for the Checkmk session. required: false type: str diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py index 9e3038d52..391d203d3 100644 --- a/plugins/module_utils/utils.py +++ b/plugins/module_utils/utils.py @@ -41,6 +41,11 @@ def base_argument_spec(): no_log=True, fallback=(env_fallback, ["CHECKMK_VAR_AUTOMATION_SECRET"]), ), + + automation_auth_type=dict( + type="str", choices=["bearer", "basic", "cookie"], default="bearer" + ), + automation_auth_cookie=dict(type="str", no_log=True), ) From 8a6fe8c1aed56067fd389b04445a5dcfd1d3cf42 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 09:58:28 +0200 Subject: [PATCH 07/16] Fix linting for DCD modules. --- plugins/lookup/dcd.py | 39 +++---- plugins/modules/dcd.py | 230 +++++++++++++++++------------------------ 2 files changed, 116 insertions(+), 153 deletions(-) diff --git a/plugins/lookup/dcd.py b/plugins/lookup/dcd.py index 6a1811ebd..606aeb2bd 100644 --- a/plugins/lookup/dcd.py +++ b/plugins/lookup/dcd.py @@ -18,8 +18,7 @@ options: _terms: - description: - - The ID of the DCD Configuration to retrieve. + description: The ID of the DCD Configuration to retrieve. required: True server_url: @@ -44,7 +43,7 @@ - section: checkmk_lookup key: site - auth_type: + automation_auth_type: description: - The authentication type to use ('bearer', 'basic', 'cookie'). required: False @@ -73,7 +72,7 @@ - section: checkmk_lookup key: automation_secret - auth_cookie: + automation_auth_cookie: description: - The authentication cookie value if using cookie-based authentication. required: False @@ -102,7 +101,7 @@ 'openshiftcluster02-dcd-object', server_url='http://127.0.0.1:32001', site='cmk', - auth_type='bearer', + automation_auth_type='bearer', automation_user='automation', automation_secret='test123', validate_certs=False @@ -148,10 +147,10 @@ class ExtendedCheckmkAPI(CheckMKLookupAPI): def __init__( self, site_url, - auth_type="bearer", + automation_auth_type="bearer", automation_user=None, automation_secret=None, - auth_cookie=None, + automation_auth_cookie=None, validate_certs=True, ): headers = { @@ -161,7 +160,7 @@ def __init__( cookies = {} # Bearer Authentication: "Bearer USERNAME PASSWORD" - if auth_type == "bearer": + if automation_auth_type == "bearer": if not automation_user or not automation_secret: raise ValueError( "`automation_user` and `automation_secret` are required for bearer authentication." @@ -169,7 +168,7 @@ def __init__( headers["Authorization"] = f"Bearer {automation_user} {automation_secret}" # Basic Authentication - elif auth_type == "basic": + elif automation_auth_type == "basic": if not automation_user or not automation_secret: raise ValueError( "`automation_user` and `automation_secret` are required for basic authentication." @@ -179,13 +178,17 @@ def __init__( headers["Authorization"] = f"Basic {auth_b64}" # Cookie Authentication - elif auth_type == "cookie": - if not auth_cookie: - raise ValueError("`auth_cookie` is required for cookie authentication.") - cookies["auth_cmk"] = auth_cookie + elif automation_auth_type == "cookie": + if not automation_auth_cookie: + raise ValueError( + "`automation_auth_cookie` is required for cookie authentication." + ) + cookies["auth_cmk"] = automation_auth_cookie else: - raise ValueError(f"Unsupported `auth_type`: {auth_type}") + raise ValueError( + f"Unsupported `automation_auth_type`: {automation_auth_type}" + ) super().__init__( site_url=site_url, user="", secret="", validate_certs=validate_certs @@ -206,10 +209,10 @@ def run(self, terms, variables=None, **kwargs): try: server_url = self.get_option("server_url") site = self.get_option("site") - auth_type = self.get_option("auth_type") or "bearer" + automation_auth_type = self.get_option("automation_auth_type") or "bearer" automation_user = self.get_option("automation_user") automation_secret = self.get_option("automation_secret") - auth_cookie = self.get_option("auth_cookie") + automation_auth_cookie = self.get_option("automation_auth_cookie") validate_certs = self.get_option("validate_certs") except KeyError as e: raise AnsibleError(f"Missing required configuration option: {str(e)}") @@ -219,10 +222,10 @@ def run(self, terms, variables=None, **kwargs): try: api = ExtendedCheckmkAPI( site_url=site_url, - auth_type=auth_type, + automation_auth_type=automation_auth_type, automation_user=automation_user, automation_secret=automation_secret, - auth_cookie=auth_cookie, + automation_auth_cookie=automation_auth_cookie, validate_certs=validate_certs, ) except ValueError as e: diff --git a/plugins/modules/dcd.py b/plugins/modules/dcd.py index 4e776e2e8..ec9360e6a 100644 --- a/plugins/modules/dcd.py +++ b/plugins/modules/dcd.py @@ -11,91 +11,90 @@ --- module: dcd -short_description: Manage Dynamic Configuration Definitions in Checkmk. +short_description: Manage Dynamic Host Management. version_added: "6.1.0" description: - - Manage Dynamic Configuration Definitions (DCD) in Checkmk, including creation, updating, and deletion. + - Manage Dynamic Host Management (DCD), including creation, updating, and deletion. extends_documentation_fragment: [checkmk.general.common] options: - - dcd_config: - description: - - Configuration parameters for the DCD. - type: dict - required: true - options: - dcd_id: - description: - - Identifier for the DCD configuration. - type: str - required: true - title: - description: - - Title of the DCD. - type: str - required: false - comment: - description: - - Description or comment for the DCD. - type: str - default: "" - site: - description: - - Name of the Checkmk site for the DCD configuration. - type: str + dcd_config: + description: Configuration parameters for the DCD. + type: dict required: true - connector_type: - description: - - Type of connector (e.g., "piggyback"). + suboptions: + dcd_id: + description: Identifier for the DCD configuration. + type: str + required: true + title: + description: Title of the DCD. + type: str + required: false + comment: + description: Description or comment for the DCD. + type: str + default: "" + site: + description: Name of the Checkmk site for the DCD configuration. + type: str + required: false + connector_type: + description: Type of connector (e.g., "piggyback"). + type: str + default: piggyback + interval: + description: Interval in seconds for DCD polling. + type: int + default: 60 + creation_rules: + description: Rules for creating hosts. + type: list + elements: dict + suboptions: + folder_path: + description: Folder path for host creation. + type: str + required: true + delete_hosts: + description: Whether to delete hosts that no longer match. + type: bool + default: false + host_attributes: + description: Additional host attributes to set on created hosts. + type: dict + exclude_time_ranges: + description: Do not activate changes during these times. + type: list + elements: str + discover_on_creation: + description: Discover services on host creation. + type: bool + default: true + restrict_source_hosts: + description: List of source hosts to restrict the DCD to. + type: list + elements: str + no_deletion_time_after_init: + description: Seconds to prevent host deletion after site startup, e.g. when booting the Checkmk server. + type: int + max_cache_age: + description: Seconds to keep hosts when piggyback source only sends piggyback data for other hosts. + type: int + validity_period: + description: Seconds to continue consider outdated piggyback data as valid. + type: int + + state: + description: Desired state of the DCD. type: str - default: piggyback - interval: - description: - - Interval in seconds for DCD polling. - type: int - default: 60 - creation_rules: - description: - - Rules for creating hosts. - type: list - elements: dict - options: - folder_path: - description: - - Folder path for host creation. - type: str - required: true - delete_hosts: - description: - - Whether to delete hosts that no longer match. - type: bool - default: false - host_attributes: - description: - - Additional host attributes to set on created hosts. - type: dict - discover_on_creation: - description: - - Discover services on host creation. - type: bool - default: true - restrict_source_hosts: - description: - - List of source hosts to restrict the DCD to. - type: list - elements: str - state: - description: - - Desired state of the DCD. - type: str - choices: - - present - - absent - default: present + choices: + - present + - absent + default: present author: - Lars Getwan (@lgetwan) @@ -106,7 +105,7 @@ checkmk.general.dcd: server_url: "http://myserver/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" dcd_config: @@ -133,7 +132,7 @@ checkmk.general.dcd: server_url: "http://myserver/" site: "mysite" - auth_type: "bearer" + automation_auth_type: "bearer" automation_user: "myuser" automation_secret: "mysecret" dcd_config: @@ -165,54 +164,16 @@ returned: when differences are detected or in diff mode """ -import base64 import json from ansible.module_utils.basic import AnsibleModule -from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI +from ansible_collections.checkmk.general.plugins.module_utils.api import ( + ExtendedCheckmkAPI, +) from ansible_collections.checkmk.general.plugins.module_utils.differ import ConfigDiffer - - -class ExtendedCheckmkAPI(CheckmkAPI): - """ - Extends CheckmkAPI to support bearer, basic, and cookie authentication methods. - """ - - def __init__(self, module): - super().__init__(module) - auth_type = self.params.get("auth_type", "bearer") - automation_user = self.params.get("automation_user") - automation_secret = self.params.get("automation_secret") - auth_cookie = self.params.get("auth_cookie") - - if auth_type == "bearer": - if not automation_user or not automation_secret: - self.module.fail_json( - msg="`automation_user` and `automation_secret` are required for bearer authentication." - ) - self.headers["Authorization"] = ( - f"Bearer {automation_user} {automation_secret}" - ) - - elif auth_type == "basic": - if not automation_user or not automation_secret: - self.module.fail_json( - msg="`automation_user` and `automation_secret` are required for basic authentication." - ) - auth_b64 = base64.b64encode( - f"{automation_user}:{automation_secret}".encode() - ).decode() - self.headers["Authorization"] = f"Basic {auth_b64}" - - elif auth_type == "cookie": - if not auth_cookie: - self.module.fail_json( - msg="`auth_cookie` is required for cookie authentication." - ) - self.cookies["auth_cmk"] = auth_cookie - - else: - self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") +from ansible_collections.checkmk.general.plugins.module_utils.utils import ( + base_argument_spec, +) class DCDHTTPCodes: @@ -525,15 +486,11 @@ def run_module(): Returns: None: The result is returned to Ansible via module.exit_json(). """ - module_args = dict( + + argument_spec = base_argument_spec() + argument_spec.update( server_url=dict(type="str", required=True), site=dict(type="str", required=True), - auth_type=dict( - type="str", choices=["bearer", "basic", "cookie"], default="bearer" - ), - automation_user=dict(type="str"), - automation_secret=dict(type="str", no_log=True), - auth_cookie=dict(type="str", no_log=True), dcd_config=dict( type="dict", required=True, @@ -553,26 +510,29 @@ def run_module(): "host_attributes": dict(type="dict", required=False), }, ), - "exclude_time_ranges": dict(type="list", required=False), + "exclude_time_ranges": dict( + type="list", elements="str", required=False + ), "discover_on_creation": dict(type="bool", default=True), - "restrict_source_hosts": dict(type="list", elements="str"), + "restrict_source_hosts": dict( + type="list", elements="str", required=False + ), "no_deletion_time_after_init": dict(type="int", required=False), "max_cache_age": dict(type="int", required=False), "validity_period": dict(type="int", required=False), }, ), state=dict(type="str", default="present", choices=["present", "absent"]), - validate_certs=dict(type="bool", default=True), ) required_if = [ - ("auth_type", "bearer", ["automation_user", "automation_secret"]), - ("auth_type", "basic", ["automation_user", "automation_secret"]), - ("auth_type", "cookie", ["auth_cookie"]), + ("automation_auth_type", "bearer", ["automation_user", "automation_secret"]), + ("automation_auth_type", "basic", ["automation_user", "automation_secret"]), + ("automation_auth_type", "cookie", ["automation_auth_cookie"]), ] module = AnsibleModule( - argument_spec=module_args, + argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, ) From e0894dac2f8cb026767862ee7d8fcc6e4b227b6f Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 09:58:53 +0200 Subject: [PATCH 08/16] Update README. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 129c012bd..f629e9f4f 100644 --- a/README.md +++ b/README.md @@ -153,12 +153,9 @@ Please do **not** consider it a concrete planning document! - Modules - Monitoring - Acknowledgement - - Business Intelligence - Event Console - Setup - Agents - - Business Intelligence - - Dynamic host management (DCD) - Event Console - Notification Rules - OMD Module From ac957b2a5821990945099499df00e8468fa7489e Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 14:41:15 +0200 Subject: [PATCH 09/16] Add first iteration of integration tests for the bi_aggregation module. --- .../ans-int-test-bi_aggregation.yaml | 117 ++++++++++++++++++ plugins/modules/bi_aggregation.py | 5 +- .../targets/bi_aggregation/tasks/main.yml | 35 ++++++ .../targets/bi_aggregation/tasks/test.yml | 60 +++++++++ .../targets/bi_aggregation/vars/main.yml | 22 ++++ 5 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ans-int-test-bi_aggregation.yaml create mode 100644 tests/integration/targets/bi_aggregation/tasks/main.yml create mode 100644 tests/integration/targets/bi_aggregation/tasks/test.yml create mode 100644 tests/integration/targets/bi_aggregation/vars/main.yml diff --git a/.github/workflows/ans-int-test-bi_aggregation.yaml b/.github/workflows/ans-int-test-bi_aggregation.yaml new file mode 100644 index 000000000..738414db4 --- /dev/null +++ b/.github/workflows/ans-int-test-bi_aggregation.yaml @@ -0,0 +1,117 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: bi_aggregation + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for BI aggregation Module +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' + pull_request: + branches: + - main + - devel + paths: + - 'plugins/modules/bi_aggregation.py' + push: + paths: + - 'plugins/modules/bi_aggregation.py' + - '.github/workflows/ans-int-test-bi_aggregation.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/plugins/modules/bi_aggregation.py b/plugins/modules/bi_aggregation.py index fd9d6924d..2778b0673 100644 --- a/plugins/modules/bi_aggregation.py +++ b/plugins/modules/bi_aggregation.py @@ -112,7 +112,7 @@ automation_secret: "mysecret" aggregation: id: "aggr1" - pack_id: "pack1" + pack_id: "default" comment: "Aggregation comment" customer: "customer1" groups: @@ -125,7 +125,6 @@ type: "empty" action: type: "state_of_host" - rule_id: "rule123" host_regex: ".*" aggregation_visualization: ignore_rule_styles: false @@ -147,7 +146,7 @@ automation_secret: "mysecret" aggregation: id: "aggr1" - pack_id: "pack1" + pack_id: "default" state: "absent" """ diff --git a/tests/integration/targets/bi_aggregation/tasks/main.yml b/tests/integration/targets/bi_aggregation/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/bi_aggregation/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/bi_aggregation/tasks/test.yml b/tests/integration/targets/bi_aggregation/tasks/test.yml new file mode 100644 index 000000000..ddcbac92e --- /dev/null +++ b/tests/integration/targets/bi_aggregation/tasks/test.yml @@ -0,0 +1,60 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: (outer_item.edition == "cme") or (outer_item.edition == "cce") + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: not ((outer_item.edition == "cme") or (outer_item.edition == "cce")) + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Create a BI aggregation." + bi_aggregation: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + aggregation: + id: "aggr1" + pack_id: "default" + comment: "Aggregation comment" + customer: "{{ (checkmk_var_customer != None) | ternary(checkmk_var_customer, omit) }}" # See PR #427 + groups: + names: ["groupA", "groupB"] + paths: + - ["path", "group", "a"] + - ["path", "group", "b"] + node: + search: + type: "empty" + action: + type: "call_a_rule" + rule_id: "host" + params: + arguments: [] + aggregation_visualization: + ignore_rule_styles: false + layout_id: "builtin_default" + line_style: "round" + computation_options: + disabled: false + use_hard_states: false + escalate_downtimes_as_warn: false + freeze_aggregations: false + state: "present" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Delete a BI aggregation." + bi_aggregation: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + aggregation: + id: "aggr1" + pack_id: "default" + state: "absent" + delegate_to: localhost diff --git a/tests/integration/targets/bi_aggregation/vars/main.yml b/tests/integration/targets/bi_aggregation/vars/main.yml new file mode 100644 index 000000000..03b82c0e7 --- /dev/null +++ b/tests/integration/targets/bi_aggregation/vars/main.yml @@ -0,0 +1,22 @@ +--- +checkmk_var_test_sites: + # - version: "2.2.0p42" + # edition: "cre" + # site: "ancient_cre" + # port: "5022" + # - version: "2.3.0p33" + # edition: "cre" + # site: "old_cre" + # port: "5023" + # - version: "2.3.0p33" + # edition: "cme" + # site: "old_cme" + # port: "5323" + # - version: "2.4.0p2" + # edition: "cre" + # site: "stable_cre" + # port: "5024" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" From cd56f82a88e633d6cb19070dede8ccbabea509d8 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 14:41:50 +0200 Subject: [PATCH 10/16] Add first iteration of integration tests for the bi_pack module. --- .github/workflows/ans-int-test-bi_pack.yaml | 117 ++++++++++++++++++ plugins/modules/bi_pack.py | 8 +- .../targets/bi_pack/tasks/main.yml | 35 ++++++ .../targets/bi_pack/tasks/test.yml | 38 ++++++ .../integration/targets/bi_pack/vars/main.yml | 22 ++++ 5 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/ans-int-test-bi_pack.yaml create mode 100644 tests/integration/targets/bi_pack/tasks/main.yml create mode 100644 tests/integration/targets/bi_pack/tasks/test.yml create mode 100644 tests/integration/targets/bi_pack/vars/main.yml diff --git a/.github/workflows/ans-int-test-bi_pack.yaml b/.github/workflows/ans-int-test-bi_pack.yaml new file mode 100644 index 000000000..6b806d96f --- /dev/null +++ b/.github/workflows/ans-int-test-bi_pack.yaml @@ -0,0 +1,117 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: bi_pack + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for BI pack Module +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' + pull_request: + branches: + - main + - devel + paths: + - 'plugins/modules/bi_pack.py' + push: + paths: + - 'plugins/modules/bi_pack.py' + - '.github/workflows/ans-int-test-bi_pack.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/plugins/modules/bi_pack.py b/plugins/modules/bi_pack.py index 46cd45054..c24c0b0b2 100644 --- a/plugins/modules/bi_pack.py +++ b/plugins/modules/bi_pack.py @@ -68,7 +68,7 @@ EXAMPLES = r""" - name: Create a BI pack with Bearer Authentication bi_pack: - server_url: "https://checkmk.example.com/" + server_url: "http://myserver/" site: "mysite" automation_auth_type: "bearer" automation_user: "myuser" @@ -84,7 +84,7 @@ - name: Create a BI pack with Basic Authentication bi_pack: - server_url: "https://checkmk.example.com/" + server_url: "http://myserver/" site: "mysite" automation_auth_type: "basic" automation_user: "basicuser" @@ -99,7 +99,7 @@ - name: Create a BI pack with Cookie Authentication bi_pack: - server_url: "https://checkmk.example.com/" + server_url: "http://myserver/" site: "mysite" automation_auth_type: "cookie" automation_auth_cookie: "sessionid=abc123xyz" @@ -113,7 +113,7 @@ - name: Delete a BI pack bi_pack: - server_url: "https://checkmk.example.com/" + server_url: "http://myserver/" site: "mysite" automation_auth_type: "bearer" automation_user: "myuser" diff --git a/tests/integration/targets/bi_pack/tasks/main.yml b/tests/integration/targets/bi_pack/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/bi_pack/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/bi_pack/tasks/test.yml b/tests/integration/targets/bi_pack/tasks/test.yml new file mode 100644 index 000000000..ac8b270db --- /dev/null +++ b/tests/integration/targets/bi_pack/tasks/test.yml @@ -0,0 +1,38 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: (outer_item.edition == "cme") or (outer_item.edition == "cce") + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: not ((outer_item.edition == "cme") or (outer_item.edition == "cce")) + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Create a BI pack." + bi_pack: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + pack: + id: "bi_pack" + title: "BI Pack" + contact_groups: + - "all" + public: true + state: "present" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Delete a BI pack." + bi_pack: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + pack: + id: "bi_pack" + state: "absent" + delegate_to: localhost diff --git a/tests/integration/targets/bi_pack/vars/main.yml b/tests/integration/targets/bi_pack/vars/main.yml new file mode 100644 index 000000000..03b82c0e7 --- /dev/null +++ b/tests/integration/targets/bi_pack/vars/main.yml @@ -0,0 +1,22 @@ +--- +checkmk_var_test_sites: + # - version: "2.2.0p42" + # edition: "cre" + # site: "ancient_cre" + # port: "5022" + # - version: "2.3.0p33" + # edition: "cre" + # site: "old_cre" + # port: "5023" + # - version: "2.3.0p33" + # edition: "cme" + # site: "old_cme" + # port: "5323" + # - version: "2.4.0p2" + # edition: "cre" + # site: "stable_cre" + # port: "5024" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" From 46a14b034629a71b67ef747123f00c2bf90a5a90 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 14:50:36 +0200 Subject: [PATCH 11/16] Update auto labeler configuration. --- .github/labels-issues.yml | 6 ++++++ .github/labels-prs.yml | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/labels-issues.yml b/.github/labels-issues.yml index 40760fc68..d2c6360f3 100644 --- a/.github/labels-issues.yml +++ b/.github/labels-issues.yml @@ -35,6 +35,9 @@ module:contact_group: module:discovery: - 'Component Name: discovery' +module:dcd: + - 'Component Name: dcd' + module:downtime: - 'Component Name: downtime' @@ -80,6 +83,9 @@ lookup:bi_pack: lookup:bi_rule: - 'Component Name: lookup_bi_rule' +lookup:dcd: + - 'Component Name: lookup_dcd' + lookup:folder: - 'Component Name: lookup_folder' diff --git a/.github/labels-prs.yml b/.github/labels-prs.yml index 0814256ab..c77d1cabe 100644 --- a/.github/labels-prs.yml +++ b/.github/labels-prs.yml @@ -54,6 +54,11 @@ module:discovery: - changed-files: - any-glob-to-any-file: 'plugins/modules/discovery.py' +module:dcd: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/dcd.py' + module:downtime: - any: - changed-files: @@ -129,6 +134,11 @@ lookup:bi_rule: - changed-files: - any-glob-to-any-file: 'plugins/modules/lookup/bi_rule.py' +lookup:dcd: + - any: + - changed-files: + - any-glob-to-any-file: 'plugins/modules/lookup/dcd.py' + lookup:folder: - any: - changed-files: From 728f20714b89cfdd1bb8d9394d221ae0352be28c Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 4 Jun 2025 13:46:28 +0200 Subject: [PATCH 12/16] Add first iteration of integration tests for the dcd module. --- .github/workflows/ans-int-test-dcd.yaml | 117 +++++++++++++++++++ tests/integration/targets/dcd/tasks/main.yml | 35 ++++++ tests/integration/targets/dcd/tasks/test.yml | 51 ++++++++ tests/integration/targets/dcd/vars/main.yml | 14 +++ 4 files changed, 217 insertions(+) create mode 100644 .github/workflows/ans-int-test-dcd.yaml create mode 100644 tests/integration/targets/dcd/tasks/main.yml create mode 100644 tests/integration/targets/dcd/tasks/test.yml create mode 100644 tests/integration/targets/dcd/vars/main.yml diff --git a/.github/workflows/ans-int-test-dcd.yaml b/.github/workflows/ans-int-test-dcd.yaml new file mode 100644 index 000000000..116580e7e --- /dev/null +++ b/.github/workflows/ans-int-test-dcd.yaml @@ -0,0 +1,117 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: bi_rule + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for BI rule Module +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' + pull_request: + branches: + - main + - devel + paths: + - 'plugins/modules/bi_rule.py' + push: + paths: + - 'plugins/modules/bi_rule.py' + - '.github/workflows/ans-int-test-bi_rule.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/tests/integration/targets/dcd/tasks/main.yml b/tests/integration/targets/dcd/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/dcd/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/dcd/tasks/test.yml b/tests/integration/targets/dcd/tasks/test.yml new file mode 100644 index 000000000..32b7e8d07 --- /dev/null +++ b/tests/integration/targets/dcd/tasks/test.yml @@ -0,0 +1,51 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: (outer_item.edition == "cme") or (outer_item.edition == "cce") + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: not ((outer_item.edition == "cme") or (outer_item.edition == "cce")) + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Create a DCD configuration." + dcd: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + dcd_config: + dcd_id: "PiggybackBoar" + title: "Piggyback Configuration for Boar" + comment: "Piggyback config for Boar host" + site: "{{ outer_item.site }}" + connector_type: "piggyback" + interval: 5 + creation_rules: + - folder_path: "/" + delete_hosts: false + host_attributes: + tag_address_family: "no-ip" + tag_agent: "special-agents" + tag_piggyback: "piggyback" + tag_snmp_ds: "no-snmp" + discover_on_creation: true + restrict_source_hosts: + - "boar" + state: "present" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Delete a DCD configuration." + dcd: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + auth_type: "bearer" + dcd_config: + dcd_id: "PiggybackBoar" + site: "{{ outer_item.site }}" + state: "absent" + delegate_to: localhost diff --git a/tests/integration/targets/dcd/vars/main.yml b/tests/integration/targets/dcd/vars/main.yml new file mode 100644 index 000000000..a27ab5b9f --- /dev/null +++ b/tests/integration/targets/dcd/vars/main.yml @@ -0,0 +1,14 @@ +--- +checkmk_var_test_sites: + # - version: "2.2.0p42" + # edition: "cce" + # site: "ancient_cce" + # port: "5222" + # - version: "2.3.0p33" + # edition: "cme" + # site: "old_cme" + # port: "5323" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" From 3e7a9aac649fcf466a8c5dee4697cd700bc7c21f Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 4 Jun 2025 13:46:51 +0200 Subject: [PATCH 13/16] Add first iteration of integration tests for the bi_rule module. --- .github/workflows/ans-int-test-bi_rule.yaml | 117 ++++++++++++++++++ plugins/modules/bi_rule.py | 4 +- .../targets/bi_rule/tasks/main.yml | 35 ++++++ .../targets/bi_rule/tasks/test.yml | 61 +++++++++ .../integration/targets/bi_rule/vars/main.yml | 22 ++++ 5 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ans-int-test-bi_rule.yaml create mode 100644 tests/integration/targets/bi_rule/tasks/main.yml create mode 100644 tests/integration/targets/bi_rule/tasks/test.yml create mode 100644 tests/integration/targets/bi_rule/vars/main.yml diff --git a/.github/workflows/ans-int-test-bi_rule.yaml b/.github/workflows/ans-int-test-bi_rule.yaml new file mode 100644 index 000000000..e6a1c683d --- /dev/null +++ b/.github/workflows/ans-int-test-bi_rule.yaml @@ -0,0 +1,117 @@ +# README: +# - When changing the module name, it needs to be changed in 'env:MODULE_NAME' and in 'on:pull_requests:path'! +# +# Resources: +# - Template for this file: https://github.com/ansible-collections/collection_template/blob/main/.github/workflows/ansible-test.yml +# - About Ansible integration tests: https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + +env: + NAMESPACE: checkmk + COLLECTION_NAME: general + MODULE_NAME: dcd + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +name: Ansible Integration Tests for the DCD Module +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' + pull_request: + branches: + - main + - devel + paths: + - 'plugins/modules/dcd.py' + push: + paths: + - 'plugins/modules/dcd.py' + - '.github/workflows/ans-int-test-dcd.yaml' + +jobs: + + integration: + runs-on: ubuntu-22.04 + name: Ⓐ${{ matrix.ansible }}+py${{ matrix.python }} + strategy: + fail-fast: false + matrix: + ansible: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - devel + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + exclude: + # Exclude unsupported sets. + - ansible: stable-2.15 + python: '3.12' + - ansible: stable-2.16 + python: '3.9' + - ansible: stable-2.17 + python: '3.9' + - ansible: devel + python: '3.9' + - ansible: devel + python: '3.10' + + services: + ancient_cre: + image: checkmk/check-mk-raw:2.2.0p42 + ports: + - 5022:5000 + env: + CMK_SITE_ID: "ancient_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cre: + image: checkmk/check-mk-raw:2.3.0p33 + ports: + - 5023:5000 + env: + CMK_SITE_ID: "old_cre" + CMK_PASSWORD: "Sup3rSec4et!" + old_cme: + image: checkmk/check-mk-managed:2.3.0p33 + ports: + - 5323:5000 + env: + CMK_SITE_ID: "old_cme" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cre: + image: checkmk/check-mk-raw:2.4.0p2 + ports: + - 5024:5000 + env: + CMK_SITE_ID: "stable_cre" + CMK_PASSWORD: "Sup3rSec4et!" + stable_cme: + image: checkmk/check-mk-managed:2.4.0p2 + ports: + - 5324:5000 + env: + CMK_SITE_ID: "stable_cme" + CMK_PASSWORD: "Sup3rSec4et!" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + path: ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Run integration test + run: ansible-test integration ${{env.MODULE_NAME}} -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} + working-directory: ./ansible_collections/${{env.NAMESPACE}}/${{env.COLLECTION_NAME}} diff --git a/plugins/modules/bi_rule.py b/plugins/modules/bi_rule.py index 741ad77f5..c86179b85 100644 --- a/plugins/modules/bi_rule.py +++ b/plugins/modules/bi_rule.py @@ -79,7 +79,7 @@ EXAMPLES = r""" - name: Create a BI rule checkmk.general.bi_rule: - server_url: "https://example.com/" + server_url: "http://myserver/" site: "mysite" automation_auth_type: "bearer" automation_user: "myuser" @@ -114,7 +114,7 @@ - name: Delete a BI rule checkmk.general.bi_rule: - server_url: "https://example.com/" + server_url: "http://myserver/" site: "mysite" automation_auth_type: "bearer" automation_user: "myuser" diff --git a/tests/integration/targets/bi_rule/tasks/main.yml b/tests/integration/targets/bi_rule/tasks/main.yml new file mode 100644 index 000000000..238480fa2 --- /dev/null +++ b/tests/integration/targets/bi_rule/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: "Include Global Variables." + ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - global.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/vars/ + - tests/integration/files/includes/vars/ + +- name: "Print Identifier." + ansible.builtin.debug: + msg: "{{ ansible_facts.system_vendor }} {{ ansible_facts.product_name }} running {{ ansible_facts.virtualization_type }}" + +- name: "Run preparations." + ansible.builtin.include_tasks: "{{ lookup('ansible.builtin.first_found', checkmk_var_params) }}" + vars: + checkmk_var_params: + files: + - prep.yml + paths: + - /home/runner/work/ansible-collection-checkmk.general/ansible-collection-checkmk.general/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - /root/ansible_collections/checkmk/general/tests/integration/files/includes/tasks/ + - tests/integration/files/includes/tasks/ + when: | + (ansible_facts.system_vendor == "Dell Inc." and 'Latitude' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + or (ansible_facts.system_vendor == "QEMU" and 'Ubuntu' in ansible_facts.product_name and ansible_facts.virtualization_type == "container") + +- name: "Testing." + ansible.builtin.include_tasks: test.yml + loop: "{{ checkmk_var_test_sites }}" + loop_control: + loop_var: outer_item diff --git a/tests/integration/targets/bi_rule/tasks/test.yml b/tests/integration/targets/bi_rule/tasks/test.yml new file mode 100644 index 000000000..fdb2a6138 --- /dev/null +++ b/tests/integration/targets/bi_rule/tasks/test.yml @@ -0,0 +1,61 @@ +--- +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Set customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: "provider" + when: (outer_item.edition == "cme") or (outer_item.edition == "cce") + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Unset customer when needed." + ansible.builtin.set_fact: + checkmk_var_customer: null + when: not ((outer_item.edition == "cme") or (outer_item.edition == "cce")) + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Create a BI rule." + bi_rule: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + rule: + pack_id: "default" + id: "testrule1" + nodes: + - search: + type: "empty" + action: + type: "call_a_rule" + rule_id: "test-child-rule1" + params: + arguments: [] + properties: + title: "Test Rule 1" + comment: "" + docu_url: "" + icon: "" + state_messages: {} + aggregation_function: + type: "best" + count: 1 + restrict_state: 2 + computation_options: + disabled: false + node_visualization: + type: "block" + style_config: {} + params: + arguments: [] + state: "present" + delegate_to: localhost + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Delete a BI rule." + bi_rule: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + automation_auth_type: "bearer" + rule: + pack_id: "cluster_pack" + id: "testrule1" + state: "absent" + delegate_to: localhost diff --git a/tests/integration/targets/bi_rule/vars/main.yml b/tests/integration/targets/bi_rule/vars/main.yml new file mode 100644 index 000000000..03b82c0e7 --- /dev/null +++ b/tests/integration/targets/bi_rule/vars/main.yml @@ -0,0 +1,22 @@ +--- +checkmk_var_test_sites: + # - version: "2.2.0p42" + # edition: "cre" + # site: "ancient_cre" + # port: "5022" + # - version: "2.3.0p33" + # edition: "cre" + # site: "old_cre" + # port: "5023" + # - version: "2.3.0p33" + # edition: "cme" + # site: "old_cme" + # port: "5323" + # - version: "2.4.0p2" + # edition: "cre" + # site: "stable_cre" + # port: "5024" + - version: "2.4.0p2" + edition: "cme" + site: "stable_cme" + port: "5324" From 6f66ce213b1487c6cbb8d8d8fe827d78de2ddd5d Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 25 Jun 2025 14:28:19 +0200 Subject: [PATCH 14/16] Fix new base arguments. --- plugins/module_utils/utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py index 391d203d3..288e7c83a 100644 --- a/plugins/module_utils/utils.py +++ b/plugins/module_utils/utils.py @@ -41,11 +41,17 @@ def base_argument_spec(): no_log=True, fallback=(env_fallback, ["CHECKMK_VAR_AUTOMATION_SECRET"]), ), - automation_auth_type=dict( - type="str", choices=["bearer", "basic", "cookie"], default="bearer" + type="str", + choices=["bearer", "basic", "cookie"], + default="bearer", + fallback=(env_fallback, ["CHECKMK_VAR_AUTH_TYPE"]), + ), + automation_auth_cookie=dict( + type="str", + no_log=True, + fallback=(env_fallback, ["CHECKMK_VAR_AUTH_COOKIE"]), ), - automation_auth_cookie=dict(type="str", no_log=True), ) From 083e464d95f876433d3458561674275b29482fc2 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 28 May 2025 11:58:34 +0200 Subject: [PATCH 15/16] Initial commit of modules donated by Atruvia AG. --- plugins/module_utils/extended_api.py | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 plugins/module_utils/extended_api.py diff --git a/plugins/module_utils/extended_api.py b/plugins/module_utils/extended_api.py new file mode 100644 index 000000000..19da7fbd0 --- /dev/null +++ b/plugins/module_utils/extended_api.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# Copyright: (c) 2025, Robin Gierse +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Ensure compatibility to Python2 +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import base64 + +from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI + + +class ExtendedCheckmkAPI(CheckmkAPI): + """ + ExtendedCheckmkAPI adds support for multiple authentication methods: bearer, basic, and cookie. + """ + + def __init__(self, module): + super().__init__(module) + auth_type = self.params.get("auth_type", "bearer") + automation_user = self.params.get("automation_user") + automation_secret = self.params.get("automation_secret") + auth_cookie = self.params.get("auth_cookie") + + # Bearer Authentication + if auth_type == "bearer": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for bearer authentication." + ) + self.headers["Authorization"] = ( + f"Bearer {automation_user} {automation_secret}" + ) + + # Basic Authentication + elif auth_type == "basic": + if not automation_user or not automation_secret: + self.module.fail_json( + msg="`automation_user` and `automation_secret` are required for basic authentication." + ) + auth_str = f"{automation_user}:{automation_secret}" + auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") + self.headers["Authorization"] = f"Basic {auth_b64}" + + # Cookie Authentication + elif auth_type == "cookie": + if not auth_cookie: + self.module.fail_json( + msg="`auth_cookie` is required for cookie authentication." + ) + self.cookies["auth_cmk"] = auth_cookie + + else: + self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}") From d15e6f6509e2b09731c723f5aba9e1b819095666 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 3 Jun 2025 09:55:23 +0200 Subject: [PATCH 16/16] Integrate extended_api.py into api.py. --- plugins/module_utils/extended_api.py | 59 ---------------------------- 1 file changed, 59 deletions(-) delete mode 100644 plugins/module_utils/extended_api.py diff --git a/plugins/module_utils/extended_api.py b/plugins/module_utils/extended_api.py deleted file mode 100644 index 19da7fbd0..000000000 --- a/plugins/module_utils/extended_api.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8; py-indent-offset: 4 -*- - -# Copyright: (c) 2025, Robin Gierse -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Ensure compatibility to Python2 -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -import base64 - -from ansible_collections.checkmk.general.plugins.module_utils.api import CheckmkAPI - - -class ExtendedCheckmkAPI(CheckmkAPI): - """ - ExtendedCheckmkAPI adds support for multiple authentication methods: bearer, basic, and cookie. - """ - - def __init__(self, module): - super().__init__(module) - auth_type = self.params.get("auth_type", "bearer") - automation_user = self.params.get("automation_user") - automation_secret = self.params.get("automation_secret") - auth_cookie = self.params.get("auth_cookie") - - # Bearer Authentication - if auth_type == "bearer": - if not automation_user or not automation_secret: - self.module.fail_json( - msg="`automation_user` and `automation_secret` are required for bearer authentication." - ) - self.headers["Authorization"] = ( - f"Bearer {automation_user} {automation_secret}" - ) - - # Basic Authentication - elif auth_type == "basic": - if not automation_user or not automation_secret: - self.module.fail_json( - msg="`automation_user` and `automation_secret` are required for basic authentication." - ) - auth_str = f"{automation_user}:{automation_secret}" - auth_b64 = base64.b64encode(auth_str.encode("utf-8")).decode("utf-8") - self.headers["Authorization"] = f"Basic {auth_b64}" - - # Cookie Authentication - elif auth_type == "cookie": - if not auth_cookie: - self.module.fail_json( - msg="`auth_cookie` is required for cookie authentication." - ) - self.cookies["auth_cmk"] = auth_cookie - - else: - self.module.fail_json(msg=f"Unsupported `auth_type`: {auth_type}")