From 6f39205d1afef3056493291c00d36afcd9331992 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Wed, 20 Dec 2023 11:12:30 +0100 Subject: [PATCH 01/23] The rule module has now a parameter rule_id that can be used to identify an existing rule. --- plugins/lookup/rules.py | 157 ++++++++++++++++++++++++++++++++++++++++ plugins/modules/rule.py | 79 ++++++++++++++------ 2 files changed, 212 insertions(+), 24 deletions(-) create mode 100644 plugins/lookup/rules.py diff --git a/plugins/lookup/rules.py b/plugins/lookup/rules.py new file mode 100644 index 000000000..2b66acdf1 --- /dev/null +++ b/plugins/lookup/rules.py @@ -0,0 +1,157 @@ +# Copyright: (c) 2023, 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: rules + author: Lars Getwan (@lgetwan) + version_added: "3.5.0" + short_description: List rules + description: + - Returns a list of Rules + options: + ruleset: + description: The ruleset name. + required: True + description_regex: + description: A regex to filter for certain descriptions. + required: False + default: "" + comment_regex: + description: A regex to filter for certain comment stings. + required: False + default: "" + server_url: + description: URL of the Checkmk server. + required: True + site: + description: Site name. + required: True + automation_user: + description: Automation user for the REST API access. + required: True + automation_secret: + description: Automation secret for the REST API access. + required: True + validate_certs: + description: Whether or not to validate TLS cerificates. + type: boolean + required: False + default: True +""" + +EXAMPLES = """ +- name: Get all rules of the ruleset host_groups + ansible.builtin.debug: + msg: "Rule: {{ item.extensions }}" + loop: "{{ + lookup('checkmk.general.rules', + ruleset='host_groups', + server_url=server_url, + site=site, + automation_user=automation_user, + automation_secret=automation_secret, + validate_certs=False + ) + }}" + loop_control: + label: "{{ item.id }}" + +- name: actice_checks:http rules that match a certain description AND comment + ansible.builtin.debug: + msg: "Rule: {{ item.extensions }}" + loop: "{{ + lookup('checkmk.general.rules', + ruleset='actice_checks:http', + description_regex='foo.*bar', + comment_regex='xmas-edition', + server_url=server_url, + site=site, + automation_user=automation_user, + automation_secret=automation_secret, + validate_certs=False + ) + }}" + loop_control: + label: "{{ item.id }}" +""" + +RETURN = """ + _list: + description: + - A list of all rules of a particular ruleset + type: list + elements: str +""" + +import json +import re + +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, **kwargs): + regex_params = {} + self.set_options(var_options=variables, direct=kwargs) + ruleset = self.get_option("ruleset") + regex_params["description"] = self.get_option("description_regex") + regex_params["comment"] = self.get_option("comment_regex") + 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") + + site_url = server_url + "/" + site + + api = CheckMKLookupAPI( + site_url=site_url, + user=user, + secret=secret, + validate_certs=validate_certs, + ) + + parameters = { + "ruleset_name": ruleset, + } + + response = json.loads(api.get("/domain-types/rule/collections/all", parameters)) + + if "code" in response: + raise AnsibleError( + "Received error for %s - %s: %s" + % ( + response.get("url", ""), + response.get("code", ""), + response.get("msg", ""), + ) + ) + + rule_list = response.get("value") + + for what, regex in regex_params.items(): + try: + if regex: + rule_list = [ + r + for r in rule_list + if re.search( + regex, + r.get("extensions", {}).get("properties", {}).get(what, ""), + ) + ] + except re.error as e: + raise AnsibleError( + "Invalid regex for %s, pattern: %s, position: %s error: %s" + % (what, e.pattern, e.pos, e.msg) + ) + + return [rule_list] diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index aa7530f8c..1e28af9a0 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -67,6 +67,11 @@ properties: description: Properties of the rule. type: dict + rule_id: + description: + - If given, it will be C(the only condition) to identify the rule to work on. + - When there's no rule found with this id, the task will fail. + type: str value_raw: description: Rule values as exported from the web interface. type: str @@ -280,6 +285,25 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): module, url, module.jsonify(params), headers=headers, method="GET" ) + if info["status"] != 200: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], str(info)), + ) + + return json.loads(response.read().decode("utf-8")).get("value") + + +def show_rule(module, base_url, headers, rule_id): + api_endpoint = "/objects/rule/" + rule_id + + url = "%s%s" % (base_url, api_endpoint) + + response, info = fetch_url( + module, url, headers=headers, method="GET" + ) + if info["status"] != 200: exit_failed( module, @@ -291,8 +315,12 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): def get_existing_rule(module, base_url, headers, ruleset, rule): - # Get rules in ruleset - rules = get_rules_in_ruleset(module, base_url, headers, ruleset) + if rule.get("rule_id"): + # We already know whih rule to get + return rule.get("rule_id") + else: + # Get rules in ruleset + rules = get_rules_in_ruleset(module, base_url, headers, ruleset) (value_mod, exc) = safe_eval(rule["value_raw"], include_exceptions=True) if exc is not None: @@ -300,7 +328,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if rules is not None: # Loop through all rules - for r in rules.get("value"): + for r in rules: (value_api, exc) = safe_eval( r["extensions"]["value_raw"], include_exceptions=True ) @@ -314,7 +342,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): and value_api == value_mod ): # If they are the same, return the ID - return r + return r["id"] return None @@ -323,9 +351,9 @@ def create_rule(module, base_url, headers, ruleset, rule): api_endpoint = "/domain-types/rule/collections/all" changed = True - e = get_existing_rule(module, base_url, headers, ruleset, rule) - if e: - return (e["id"], not changed) + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + if rule_id: + return (rule_id, not changed) if module.check_mode: return (None, changed) @@ -358,10 +386,11 @@ def create_rule(module, base_url, headers, ruleset, rule): def delete_rule(module, base_url, headers, ruleset, rule): changed = True - e = get_existing_rule(module, base_url, headers, ruleset, rule) - if e: + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + + if rule_id: if not module.check_mode: - delete_rule_by_id(module, base_url, headers, e["id"]) + delete_rule_by_id(module, base_url, headers, rule_id) return changed return not changed @@ -447,6 +476,7 @@ def run_module(): conditions=dict(type="dict"), properties=dict(type="dict"), value_raw=dict(type="str"), + rule_id=dict(type="str"), location=dict( type="dict", options=dict( @@ -495,23 +525,24 @@ def run_module(): # Get the variables ruleset = module.params.get("ruleset", "") - rule = module.params.get("rule", "") + rule = module.params.get("rule", {}) location = rule.get("location") # Check if required params to create a rule are given - if rule.get("folder") is None or rule.get("folder") == "": - rule["folder"] = location["folder"] - if rule.get("properties") is None or rule.get("properties") == "": - exit_failed(module, "Rule properties are required") - if rule.get("value_raw") is None or rule.get("value_raw") == "": - exit_failed(module, "Rule value_raw is required") - # Default to all hosts if conditions arent given - if rule.get("conditions") is None or rule.get("conditions") == "": - rule["conditions"] = { - "host_tags": [], - "host_labels": [], - "service_labels": [], - } + if rule.get("rule_id") is None or rule.get("rule_id") == "": + if rule.get("folder") is None or rule.get("folder") == "": + rule["folder"] = location["folder"] + if rule.get("properties") is None or rule.get("properties") == "": + exit_failed(module, "Rule properties are required") + if rule.get("value_raw") is None or rule.get("value_raw") == "": + exit_failed(module, "Rule value_raw is required") + # Default to all hosts if conditions arent given + if rule.get("conditions") is None or rule.get("conditions") == "": + rule["conditions"] = { + "host_tags": [], + "host_labels": [], + "service_labels": [], + } if module.params.get("state") == "absent": if location.get("rule_id") is not None: exit_failed(module, "rule_id in location is invalid with state=absent") From 134836a301782481bc3b366914558da388692b8c Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 9 Jan 2024 07:48:03 +0100 Subject: [PATCH 02/23] Ongoing development. --- plugins/modules/rule.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 1e28af9a0..885f3d091 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -317,7 +317,10 @@ def show_rule(module, base_url, headers, rule_id): def get_existing_rule(module, base_url, headers, ruleset, rule): if rule.get("rule_id"): # We already know whih rule to get - return rule.get("rule_id") + if module.params.get("state") == "absent": + # When deleting and we already know the ID, don't compare + return rule.get("rule_id") + rules = [ show_rule(module, base_url, headers, rule.get("rule_id")) ] else: # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) @@ -529,9 +532,9 @@ def run_module(): location = rule.get("location") # Check if required params to create a rule are given + if rule.get("folder") is None or rule.get("folder") == "": + rule["folder"] = location["folder"] if rule.get("rule_id") is None or rule.get("rule_id") == "": - if rule.get("folder") is None or rule.get("folder") == "": - rule["folder"] = location["folder"] if rule.get("properties") is None or rule.get("properties") == "": exit_failed(module, "Rule properties are required") if rule.get("value_raw") is None or rule.get("value_raw") == "": From f23018754e2be31c773b0e80b43a8c3a7328361d Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:01:22 +0100 Subject: [PATCH 03/23] Now it's also possible to change existing rules based on a given rule_id. --- plugins/modules/rule.py | 65 +++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 885f3d091..f7f352d6a 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -387,6 +387,42 @@ def create_rule(module, base_url, headers, ruleset, rule): return (r["id"], changed) +def modify_rule(module, base_url, headers, ruleset, rule): + changed = True + rule_id = rule.get("rule_id") + + if not rule_id: + return not changed + + if module.check_mode: + return (None, changed) + + headers["If-Match"] = get_rule_etag(module, base_url, headers, rule_id) + + params = { + "properties": rule["properties"], + "value_raw": rule["value_raw"], + "conditions": rule["conditions"], + } + + api_endpoint = "/objects/rule/" + rule_id + url = base_url + api_endpoint + + info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="PUT" + )[1] + #exit_failed(module, "###### INFO: %s" % str(info)) + + if info["status"] not in [200, 204]: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), + ) + + return changed + + def delete_rule(module, base_url, headers, ruleset, rule): changed = True rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) @@ -532,15 +568,15 @@ def run_module(): location = rule.get("location") # Check if required params to create a rule are given - if rule.get("folder") is None or rule.get("folder") == "": + if not rule.get("folder"): rule["folder"] = location["folder"] - if rule.get("rule_id") is None or rule.get("rule_id") == "": - if rule.get("properties") is None or rule.get("properties") == "": + if not rule.get("rule_id"): + if not rule.get("properties"): exit_failed(module, "Rule properties are required") - if rule.get("value_raw") is None or rule.get("value_raw") == "": + if not rule.get("value_raw"): exit_failed(module, "Rule value_raw is required") # Default to all hosts if conditions arent given - if rule.get("conditions") is None or rule.get("conditions") == "": + if rule.get("conditions"): rule["conditions"] = { "host_tags": [], "host_labels": [], @@ -559,13 +595,24 @@ def run_module(): exit_ok(module, "Rule does not exist") # If state is present, create the rule elif module.params.get("state") == "present": - (rule_id, created) = create_rule(module, base_url, headers, ruleset, rule) - if created: + action = None + if rule.get("rule_id"): + # Modify an existing rule + rule_id = rule.get("rule_id") + if modify_rule(module, base_url, headers, ruleset, rule): + action = "changed" + else: + # If no rule_id is mentioned, we check if our rule exists. If not, then create it. + (rule_id, changed) = create_rule(module, base_url, headers, ruleset, rule) + if changed: + action = "created" + + if action: # Move rule to specified location, if it's not default if location["position"] != "bottom" and not module.check_mode: move_rule(module, base_url, headers, rule_id, location) - exit_changed(module, "Rule created", rule_id) - exit_ok(module, "Rule already exists", rule_id) + exit_changed(module, "Rule %s" % action, rule_id) + exit_ok(module, "Rule already exists with equal settings", rule_id) # Fallback exit_failed(module, "Unknown error") From 48f34296229f224528669bf3cc6b75abe7eda07d Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Wed, 20 Dec 2023 11:12:30 +0100 Subject: [PATCH 04/23] The rule module has now a parameter rule_id that can be used to identify an existing rule. --- plugins/modules/rule.py | 79 ++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index aa7530f8c..1e28af9a0 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -67,6 +67,11 @@ properties: description: Properties of the rule. type: dict + rule_id: + description: + - If given, it will be C(the only condition) to identify the rule to work on. + - When there's no rule found with this id, the task will fail. + type: str value_raw: description: Rule values as exported from the web interface. type: str @@ -280,6 +285,25 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): module, url, module.jsonify(params), headers=headers, method="GET" ) + if info["status"] != 200: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], str(info)), + ) + + return json.loads(response.read().decode("utf-8")).get("value") + + +def show_rule(module, base_url, headers, rule_id): + api_endpoint = "/objects/rule/" + rule_id + + url = "%s%s" % (base_url, api_endpoint) + + response, info = fetch_url( + module, url, headers=headers, method="GET" + ) + if info["status"] != 200: exit_failed( module, @@ -291,8 +315,12 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): def get_existing_rule(module, base_url, headers, ruleset, rule): - # Get rules in ruleset - rules = get_rules_in_ruleset(module, base_url, headers, ruleset) + if rule.get("rule_id"): + # We already know whih rule to get + return rule.get("rule_id") + else: + # Get rules in ruleset + rules = get_rules_in_ruleset(module, base_url, headers, ruleset) (value_mod, exc) = safe_eval(rule["value_raw"], include_exceptions=True) if exc is not None: @@ -300,7 +328,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if rules is not None: # Loop through all rules - for r in rules.get("value"): + for r in rules: (value_api, exc) = safe_eval( r["extensions"]["value_raw"], include_exceptions=True ) @@ -314,7 +342,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): and value_api == value_mod ): # If they are the same, return the ID - return r + return r["id"] return None @@ -323,9 +351,9 @@ def create_rule(module, base_url, headers, ruleset, rule): api_endpoint = "/domain-types/rule/collections/all" changed = True - e = get_existing_rule(module, base_url, headers, ruleset, rule) - if e: - return (e["id"], not changed) + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + if rule_id: + return (rule_id, not changed) if module.check_mode: return (None, changed) @@ -358,10 +386,11 @@ def create_rule(module, base_url, headers, ruleset, rule): def delete_rule(module, base_url, headers, ruleset, rule): changed = True - e = get_existing_rule(module, base_url, headers, ruleset, rule) - if e: + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + + if rule_id: if not module.check_mode: - delete_rule_by_id(module, base_url, headers, e["id"]) + delete_rule_by_id(module, base_url, headers, rule_id) return changed return not changed @@ -447,6 +476,7 @@ def run_module(): conditions=dict(type="dict"), properties=dict(type="dict"), value_raw=dict(type="str"), + rule_id=dict(type="str"), location=dict( type="dict", options=dict( @@ -495,23 +525,24 @@ def run_module(): # Get the variables ruleset = module.params.get("ruleset", "") - rule = module.params.get("rule", "") + rule = module.params.get("rule", {}) location = rule.get("location") # Check if required params to create a rule are given - if rule.get("folder") is None or rule.get("folder") == "": - rule["folder"] = location["folder"] - if rule.get("properties") is None or rule.get("properties") == "": - exit_failed(module, "Rule properties are required") - if rule.get("value_raw") is None or rule.get("value_raw") == "": - exit_failed(module, "Rule value_raw is required") - # Default to all hosts if conditions arent given - if rule.get("conditions") is None or rule.get("conditions") == "": - rule["conditions"] = { - "host_tags": [], - "host_labels": [], - "service_labels": [], - } + if rule.get("rule_id") is None or rule.get("rule_id") == "": + if rule.get("folder") is None or rule.get("folder") == "": + rule["folder"] = location["folder"] + if rule.get("properties") is None or rule.get("properties") == "": + exit_failed(module, "Rule properties are required") + if rule.get("value_raw") is None or rule.get("value_raw") == "": + exit_failed(module, "Rule value_raw is required") + # Default to all hosts if conditions arent given + if rule.get("conditions") is None or rule.get("conditions") == "": + rule["conditions"] = { + "host_tags": [], + "host_labels": [], + "service_labels": [], + } if module.params.get("state") == "absent": if location.get("rule_id") is not None: exit_failed(module, "rule_id in location is invalid with state=absent") From 059df3daaa0e59fa6b56bba403b9ee946aa06120 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 9 Jan 2024 07:48:03 +0100 Subject: [PATCH 05/23] Ongoing development. --- plugins/modules/rule.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 1e28af9a0..885f3d091 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -317,7 +317,10 @@ def show_rule(module, base_url, headers, rule_id): def get_existing_rule(module, base_url, headers, ruleset, rule): if rule.get("rule_id"): # We already know whih rule to get - return rule.get("rule_id") + if module.params.get("state") == "absent": + # When deleting and we already know the ID, don't compare + return rule.get("rule_id") + rules = [ show_rule(module, base_url, headers, rule.get("rule_id")) ] else: # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) @@ -529,9 +532,9 @@ def run_module(): location = rule.get("location") # Check if required params to create a rule are given + if rule.get("folder") is None or rule.get("folder") == "": + rule["folder"] = location["folder"] if rule.get("rule_id") is None or rule.get("rule_id") == "": - if rule.get("folder") is None or rule.get("folder") == "": - rule["folder"] = location["folder"] if rule.get("properties") is None or rule.get("properties") == "": exit_failed(module, "Rule properties are required") if rule.get("value_raw") is None or rule.get("value_raw") == "": From f740143d28a327bbd678c89896bb44ef47e43142 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:01:22 +0100 Subject: [PATCH 06/23] Now it's also possible to change existing rules based on a given rule_id. --- plugins/modules/rule.py | 65 +++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 885f3d091..f7f352d6a 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -387,6 +387,42 @@ def create_rule(module, base_url, headers, ruleset, rule): return (r["id"], changed) +def modify_rule(module, base_url, headers, ruleset, rule): + changed = True + rule_id = rule.get("rule_id") + + if not rule_id: + return not changed + + if module.check_mode: + return (None, changed) + + headers["If-Match"] = get_rule_etag(module, base_url, headers, rule_id) + + params = { + "properties": rule["properties"], + "value_raw": rule["value_raw"], + "conditions": rule["conditions"], + } + + api_endpoint = "/objects/rule/" + rule_id + url = base_url + api_endpoint + + info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="PUT" + )[1] + #exit_failed(module, "###### INFO: %s" % str(info)) + + if info["status"] not in [200, 204]: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), + ) + + return changed + + def delete_rule(module, base_url, headers, ruleset, rule): changed = True rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) @@ -532,15 +568,15 @@ def run_module(): location = rule.get("location") # Check if required params to create a rule are given - if rule.get("folder") is None or rule.get("folder") == "": + if not rule.get("folder"): rule["folder"] = location["folder"] - if rule.get("rule_id") is None or rule.get("rule_id") == "": - if rule.get("properties") is None or rule.get("properties") == "": + if not rule.get("rule_id"): + if not rule.get("properties"): exit_failed(module, "Rule properties are required") - if rule.get("value_raw") is None or rule.get("value_raw") == "": + if not rule.get("value_raw"): exit_failed(module, "Rule value_raw is required") # Default to all hosts if conditions arent given - if rule.get("conditions") is None or rule.get("conditions") == "": + if rule.get("conditions"): rule["conditions"] = { "host_tags": [], "host_labels": [], @@ -559,13 +595,24 @@ def run_module(): exit_ok(module, "Rule does not exist") # If state is present, create the rule elif module.params.get("state") == "present": - (rule_id, created) = create_rule(module, base_url, headers, ruleset, rule) - if created: + action = None + if rule.get("rule_id"): + # Modify an existing rule + rule_id = rule.get("rule_id") + if modify_rule(module, base_url, headers, ruleset, rule): + action = "changed" + else: + # If no rule_id is mentioned, we check if our rule exists. If not, then create it. + (rule_id, changed) = create_rule(module, base_url, headers, ruleset, rule) + if changed: + action = "created" + + if action: # Move rule to specified location, if it's not default if location["position"] != "bottom" and not module.check_mode: move_rule(module, base_url, headers, rule_id, location) - exit_changed(module, "Rule created", rule_id) - exit_ok(module, "Rule already exists", rule_id) + exit_changed(module, "Rule %s" % action, rule_id) + exit_ok(module, "Rule already exists with equal settings", rule_id) # Fallback exit_failed(module, "Unknown error") From add9f81351447ab070a969bdcf642fb244d3e398 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:52:24 +0100 Subject: [PATCH 07/23] Modifying existing rules is now idempotent, at least for some of the rulesets. Depends on the value_raw that is used. --- plugins/modules/rule.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index f7f352d6a..93d0c1ab5 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -394,6 +394,9 @@ def modify_rule(module, base_url, headers, ruleset, rule): if not rule_id: return not changed + if get_existing_rule(module, base_url, headers, ruleset, rule): + return not changed + if module.check_mode: return (None, changed) From 1b51ae97ccf7ba3ab131aad51ce4618193518612 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:53:22 +0100 Subject: [PATCH 08/23] Typo in the rules lookup module integration test. --- tests/integration/targets/lookup_rules/tasks/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/lookup_rules/tasks/test.yml b/tests/integration/targets/lookup_rules/tasks/test.yml index b42e3dc6c..0406251e9 100644 --- a/tests/integration/targets/lookup_rules/tasks/test.yml +++ b/tests/integration/targets/lookup_rules/tasks/test.yml @@ -68,7 +68,7 @@ vars: rules: "{{ lookup('checkmk.general.rules', ruleset=item, - commebt_regex='Ansible managed', + comment_regex='Ansible managed', server_url=server_url, site=outer_item.site, validate_certs=False, From d30d7a7c48804f170569057462634c312439e0d3 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:53:56 +0100 Subject: [PATCH 09/23] Added an integration test case for modifying existing rules. --- tests/integration/targets/rule/tasks/test.yml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index b2f13c23c..c74f1b643 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -45,6 +45,45 @@ delegate_to: localhost run_once: true # noqa run-once[task] +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Modify rules." + rule: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + ruleset: "{{ item.ruleset }}" + rule: + rule_id: "{{ existing_rule[0].id }}" + properties: { + "description": "Modified this intentionally." + } + state: "present" + vars: + existing_rule:"{{ lookup('checkmk.general.rules', + ruleset=item.ruleset, + comment_regex='Ansible managed', + server_url=server_url, + site=outer_item.site, + validate_certs=False, + automation_user=automation_user, + automation_secret=automation_secret) + }}" + delegate_to: localhost + run_once: true # noqa run-once[task] + loop: "{{ checkmk_var_rules }}" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Activate." + activation: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + force_foreign_changes: true + sites: + - "{{ outer_item.site }}" + delegate_to: localhost + run_once: true # noqa run-once[task] + - name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Delete rules." rule: server_url: "{{ checkmk_var_server_url }}" From fb5d4aedddc98151115113eac51f9a0e25dc864c Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:57:45 +0100 Subject: [PATCH 10/23] Sanity --- plugins/modules/rule.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 93d0c1ab5..9971e0c46 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -68,7 +68,7 @@ description: Properties of the rule. type: dict rule_id: - description: + description: - If given, it will be C(the only condition) to identify the rule to work on. - When there's no rule found with this id, the task will fail. type: str @@ -300,9 +300,7 @@ def show_rule(module, base_url, headers, rule_id): url = "%s%s" % (base_url, api_endpoint) - response, info = fetch_url( - module, url, headers=headers, method="GET" - ) + response, info = fetch_url(module, url, headers=headers, method="GET") if info["status"] != 200: exit_failed( @@ -320,7 +318,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if module.params.get("state") == "absent": # When deleting and we already know the ID, don't compare return rule.get("rule_id") - rules = [ show_rule(module, base_url, headers, rule.get("rule_id")) ] + rules = [show_rule(module, base_url, headers, rule.get("rule_id"))] else: # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) @@ -414,7 +412,6 @@ def modify_rule(module, base_url, headers, ruleset, rule): info = fetch_url( module, url, module.jsonify(params), headers=headers, method="PUT" )[1] - #exit_failed(module, "###### INFO: %s" % str(info)) if info["status"] not in [200, 204]: exit_failed( From 409ee8ea8278437093f6b285c9ce51bb3d1106b0 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 11:12:21 +0100 Subject: [PATCH 11/23] Typo in rule integration test. --- tests/integration/targets/rule/tasks/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index c74f1b643..8029dab10 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -59,7 +59,7 @@ } state: "present" vars: - existing_rule:"{{ lookup('checkmk.general.rules', + existing_rule: "{{ lookup('checkmk.general.rules', ruleset=item.ruleset, comment_regex='Ansible managed', server_url=server_url, From 311885c9cfcb1d8bc3e7c55db30a030c0fc7cdf8 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 11:29:08 +0100 Subject: [PATCH 12/23] Copy & paste issue in rule integration test. --- tests/integration/targets/rule/tasks/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index 8029dab10..541edcb0e 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -62,11 +62,11 @@ existing_rule: "{{ lookup('checkmk.general.rules', ruleset=item.ruleset, comment_regex='Ansible managed', - server_url=server_url, + server_url=checkmk_var_server_url, site=outer_item.site, validate_certs=False, - automation_user=automation_user, - automation_secret=automation_secret) + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) }}" delegate_to: localhost run_once: true # noqa run-once[task] From 24dc453686d265d0f1773018e3dd3aa7b3ac22a2 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 23 Jan 2024 09:03:45 +0100 Subject: [PATCH 13/23] Debugged the integration test for the rules module. --- tests/integration/targets/rule/tasks/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index 541edcb0e..f380dd7b0 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -55,9 +55,14 @@ rule: rule_id: "{{ existing_rule[0].id }}" properties: { - "description": "Modified this intentionally." + "comment": "{{ existing_rule[0].extensions.properties.comment }}", + "description": "Modified this intentionally.", + "disabled": "{{ existing_rule[0].extensions.properties.disabled }}" } + conditions: "{{ existing_rule[0].extensions.conditions }}" + value_raw: "{{ existing_rule[0].extensions.value_raw | string }}" state: "present" + when: "existing_rule|length>0" vars: existing_rule: "{{ lookup('checkmk.general.rules', ruleset=item.ruleset, From 2c3a9c65a2413538d762b1dc11b19ceaa39ddeec Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Wed, 20 Dec 2023 11:12:30 +0100 Subject: [PATCH 14/23] The rule module has now a parameter rule_id that can be used to identify an existing rule. --- plugins/modules/rule.py | 79 ++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 097acce4e..959196536 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -67,6 +67,11 @@ properties: description: Properties of the rule. type: dict + rule_id: + description: + - If given, it will be C(the only condition) to identify the rule to work on. + - When there's no rule found with this id, the task will fail. + type: str value_raw: description: Rule values as exported from the web interface. type: str @@ -280,6 +285,25 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): module, url, module.jsonify(params), headers=headers, method="GET" ) + if info["status"] != 200: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], str(info)), + ) + + return json.loads(response.read().decode("utf-8")).get("value") + + +def show_rule(module, base_url, headers, rule_id): + api_endpoint = "/objects/rule/" + rule_id + + url = "%s%s" % (base_url, api_endpoint) + + response, info = fetch_url( + module, url, headers=headers, method="GET" + ) + if info["status"] != 200: exit_failed( module, @@ -308,8 +332,12 @@ def get_rule_by_id(module, base_url, headers, rule_id): def get_existing_rule(module, base_url, headers, ruleset, rule): - # Get rules in ruleset - rules = get_rules_in_ruleset(module, base_url, headers, ruleset) + if rule.get("rule_id"): + # We already know whih rule to get + return rule.get("rule_id") + else: + # Get rules in ruleset + rules = get_rules_in_ruleset(module, base_url, headers, ruleset) (value_mod, exc) = safe_eval(rule["value_raw"], include_exceptions=True) if exc is not None: @@ -324,7 +352,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if rules is not None: # Loop through all rules - for r in rules.get("value"): + for r in rules: (value_api, exc) = safe_eval( r["extensions"]["value_raw"], include_exceptions=True ) @@ -338,7 +366,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): and value_api == value_mod ): # If they are the same, return the ID - return r + return r["id"] return None @@ -347,9 +375,9 @@ def create_rule(module, base_url, headers, ruleset, rule): api_endpoint = "/domain-types/rule/collections/all" changed = True - e = get_existing_rule(module, base_url, headers, ruleset, rule) - if e: - return (e["id"], not changed) + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + if rule_id: + return (rule_id, not changed) if module.check_mode: return (None, changed) @@ -382,10 +410,11 @@ def create_rule(module, base_url, headers, ruleset, rule): def delete_rule(module, base_url, headers, ruleset, rule): changed = True - e = get_existing_rule(module, base_url, headers, ruleset, rule) - if e: + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + + if rule_id: if not module.check_mode: - delete_rule_by_id(module, base_url, headers, e["id"]) + delete_rule_by_id(module, base_url, headers, rule_id) return changed return not changed @@ -471,6 +500,7 @@ def run_module(): conditions=dict(type="dict"), properties=dict(type="dict"), value_raw=dict(type="str"), + rule_id=dict(type="str"), location=dict( type="dict", options=dict( @@ -519,23 +549,24 @@ def run_module(): # Get the variables ruleset = module.params.get("ruleset", "") - rule = module.params.get("rule", "") + rule = module.params.get("rule", {}) location = rule.get("location") # Check if required params to create a rule are given - if rule.get("folder") is None or rule.get("folder") == "": - rule["folder"] = location["folder"] - if rule.get("properties") is None or rule.get("properties") == "": - exit_failed(module, "Rule properties are required") - if rule.get("value_raw") is None or rule.get("value_raw") == "": - exit_failed(module, "Rule value_raw is required") - # Default to all hosts if conditions arent given - if rule.get("conditions") is None or rule.get("conditions") == "": - rule["conditions"] = { - "host_tags": [], - "host_labels": [], - "service_labels": [], - } + if rule.get("rule_id") is None or rule.get("rule_id") == "": + if rule.get("folder") is None or rule.get("folder") == "": + rule["folder"] = location["folder"] + if rule.get("properties") is None or rule.get("properties") == "": + exit_failed(module, "Rule properties are required") + if rule.get("value_raw") is None or rule.get("value_raw") == "": + exit_failed(module, "Rule value_raw is required") + # Default to all hosts if conditions arent given + if rule.get("conditions") is None or rule.get("conditions") == "": + rule["conditions"] = { + "host_tags": [], + "host_labels": [], + "service_labels": [], + } if module.params.get("state") == "absent": if location.get("rule_id") is not None: exit_failed(module, "rule_id in location is invalid with state=absent") From 8f7d4a63e6187eaedeff45807e986800b65d0a3b Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 9 Jan 2024 07:48:03 +0100 Subject: [PATCH 15/23] Ongoing development. --- plugins/modules/rule.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 959196536..990594d97 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -334,7 +334,10 @@ def get_rule_by_id(module, base_url, headers, rule_id): def get_existing_rule(module, base_url, headers, ruleset, rule): if rule.get("rule_id"): # We already know whih rule to get - return rule.get("rule_id") + if module.params.get("state") == "absent": + # When deleting and we already know the ID, don't compare + return rule.get("rule_id") + rules = [ show_rule(module, base_url, headers, rule.get("rule_id")) ] else: # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) @@ -553,9 +556,9 @@ def run_module(): location = rule.get("location") # Check if required params to create a rule are given + if rule.get("folder") is None or rule.get("folder") == "": + rule["folder"] = location["folder"] if rule.get("rule_id") is None or rule.get("rule_id") == "": - if rule.get("folder") is None or rule.get("folder") == "": - rule["folder"] = location["folder"] if rule.get("properties") is None or rule.get("properties") == "": exit_failed(module, "Rule properties are required") if rule.get("value_raw") is None or rule.get("value_raw") == "": From b39fd6d6eadd1243b6ccce63b320c2ca1034efbf Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:01:22 +0100 Subject: [PATCH 16/23] Now it's also possible to change existing rules based on a given rule_id. --- plugins/modules/rule.py | 65 +++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 990594d97..61c1331d1 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -411,6 +411,42 @@ def create_rule(module, base_url, headers, ruleset, rule): return (r["id"], changed) +def modify_rule(module, base_url, headers, ruleset, rule): + changed = True + rule_id = rule.get("rule_id") + + if not rule_id: + return not changed + + if module.check_mode: + return (None, changed) + + headers["If-Match"] = get_rule_etag(module, base_url, headers, rule_id) + + params = { + "properties": rule["properties"], + "value_raw": rule["value_raw"], + "conditions": rule["conditions"], + } + + api_endpoint = "/objects/rule/" + rule_id + url = base_url + api_endpoint + + info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="PUT" + )[1] + #exit_failed(module, "###### INFO: %s" % str(info)) + + if info["status"] not in [200, 204]: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), + ) + + return changed + + def delete_rule(module, base_url, headers, ruleset, rule): changed = True rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) @@ -556,15 +592,15 @@ def run_module(): location = rule.get("location") # Check if required params to create a rule are given - if rule.get("folder") is None or rule.get("folder") == "": + if not rule.get("folder"): rule["folder"] = location["folder"] - if rule.get("rule_id") is None or rule.get("rule_id") == "": - if rule.get("properties") is None or rule.get("properties") == "": + if not rule.get("rule_id"): + if not rule.get("properties"): exit_failed(module, "Rule properties are required") - if rule.get("value_raw") is None or rule.get("value_raw") == "": + if not rule.get("value_raw"): exit_failed(module, "Rule value_raw is required") # Default to all hosts if conditions arent given - if rule.get("conditions") is None or rule.get("conditions") == "": + if rule.get("conditions"): rule["conditions"] = { "host_tags": [], "host_labels": [], @@ -583,13 +619,24 @@ def run_module(): exit_ok(module, "Rule does not exist") # If state is present, create the rule elif module.params.get("state") == "present": - (rule_id, created) = create_rule(module, base_url, headers, ruleset, rule) - if created: + action = None + if rule.get("rule_id"): + # Modify an existing rule + rule_id = rule.get("rule_id") + if modify_rule(module, base_url, headers, ruleset, rule): + action = "changed" + else: + # If no rule_id is mentioned, we check if our rule exists. If not, then create it. + (rule_id, changed) = create_rule(module, base_url, headers, ruleset, rule) + if changed: + action = "created" + + if action: # Move rule to specified location, if it's not default if location["position"] != "bottom" and not module.check_mode: move_rule(module, base_url, headers, rule_id, location) - exit_changed(module, "Rule created", rule_id) - exit_ok(module, "Rule already exists", rule_id) + exit_changed(module, "Rule %s" % action, rule_id) + exit_ok(module, "Rule already exists with equal settings", rule_id) # Fallback exit_failed(module, "Unknown error") From ed320f64eeba70fccca331c9aa889dcbd96c445d Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Wed, 20 Dec 2023 11:12:30 +0100 Subject: [PATCH 17/23] The rule module has now a parameter rule_id that can be used to identify an existing rule. --- plugins/modules/rule.py | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 61c1331d1..9971e0c46 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -68,7 +68,7 @@ description: Properties of the rule. type: dict rule_id: - description: + description: - If given, it will be C(the only condition) to identify the rule to work on. - When there's no rule found with this id, the task will fail. type: str @@ -300,28 +300,9 @@ def show_rule(module, base_url, headers, rule_id): url = "%s%s" % (base_url, api_endpoint) - response, info = fetch_url( - module, url, headers=headers, method="GET" - ) - - if info["status"] != 200: - exit_failed( - module, - "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), - ) - - return json.loads(response.read().decode("utf-8")) - - -def get_rule_by_id(module, base_url, headers, rule_id): - api_endpoint = "/objects/rule/" + rule_id - - url = base_url + api_endpoint - response, info = fetch_url(module, url, headers=headers, method="GET") - if info["status"] not in [200, 204]: + if info["status"] != 200: exit_failed( module, "Error calling API. HTTP code %d. Details: %s, " @@ -337,7 +318,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if module.params.get("state") == "absent": # When deleting and we already know the ID, don't compare return rule.get("rule_id") - rules = [ show_rule(module, base_url, headers, rule.get("rule_id")) ] + rules = [show_rule(module, base_url, headers, rule.get("rule_id"))] else: # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) @@ -346,13 +327,6 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if exc is not None: exit_failed(module, "value_raw in rule has invalid format") - # Get folder from neighbour rule if relative rule_id is given in location - if rule["location"]["rule_id"] is not None: - neighbour_rule = get_rule_by_id( - module, base_url, headers, rule["location"]["rule_id"] - ) - rule["folder"] = neighbour_rule["extensions"]["folder"] - if rules is not None: # Loop through all rules for r in rules: @@ -418,6 +392,9 @@ def modify_rule(module, base_url, headers, ruleset, rule): if not rule_id: return not changed + if get_existing_rule(module, base_url, headers, ruleset, rule): + return not changed + if module.check_mode: return (None, changed) @@ -435,7 +412,6 @@ def modify_rule(module, base_url, headers, ruleset, rule): info = fetch_url( module, url, module.jsonify(params), headers=headers, method="PUT" )[1] - #exit_failed(module, "###### INFO: %s" % str(info)) if info["status"] not in [200, 204]: exit_failed( From aad310f58d11a718d8459e67a5d226b656f5123f Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:53:22 +0100 Subject: [PATCH 18/23] Typo in the rules lookup module integration test. --- tests/integration/targets/lookup_rules/tasks/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/lookup_rules/tasks/test.yml b/tests/integration/targets/lookup_rules/tasks/test.yml index b42e3dc6c..0406251e9 100644 --- a/tests/integration/targets/lookup_rules/tasks/test.yml +++ b/tests/integration/targets/lookup_rules/tasks/test.yml @@ -68,7 +68,7 @@ vars: rules: "{{ lookup('checkmk.general.rules', ruleset=item, - commebt_regex='Ansible managed', + comment_regex='Ansible managed', server_url=server_url, site=outer_item.site, validate_certs=False, From 461e4b94310ad9f01b68a2c59b9f3ebd00d07d52 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 10:53:56 +0100 Subject: [PATCH 19/23] Added an integration test case for modifying existing rules. --- tests/integration/targets/rule/tasks/test.yml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index b2f13c23c..c74f1b643 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -45,6 +45,45 @@ delegate_to: localhost run_once: true # noqa run-once[task] +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Modify rules." + rule: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + ruleset: "{{ item.ruleset }}" + rule: + rule_id: "{{ existing_rule[0].id }}" + properties: { + "description": "Modified this intentionally." + } + state: "present" + vars: + existing_rule:"{{ lookup('checkmk.general.rules', + ruleset=item.ruleset, + comment_regex='Ansible managed', + server_url=server_url, + site=outer_item.site, + validate_certs=False, + automation_user=automation_user, + automation_secret=automation_secret) + }}" + delegate_to: localhost + run_once: true # noqa run-once[task] + loop: "{{ checkmk_var_rules }}" + +- name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Activate." + activation: + server_url: "{{ checkmk_var_server_url }}" + site: "{{ outer_item.site }}" + automation_user: "{{ checkmk_var_automation_user }}" + automation_secret: "{{ checkmk_var_automation_secret }}" + force_foreign_changes: true + sites: + - "{{ outer_item.site }}" + delegate_to: localhost + run_once: true # noqa run-once[task] + - name: "{{ outer_item.version }} - {{ outer_item.edition | upper }} - Delete rules." rule: server_url: "{{ checkmk_var_server_url }}" From 8407c415d40f65ec91e1f2e6b7301f35dbff78a1 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 11:12:21 +0100 Subject: [PATCH 20/23] Typo in rule integration test. --- tests/integration/targets/rule/tasks/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index c74f1b643..8029dab10 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -59,7 +59,7 @@ } state: "present" vars: - existing_rule:"{{ lookup('checkmk.general.rules', + existing_rule: "{{ lookup('checkmk.general.rules', ruleset=item.ruleset, comment_regex='Ansible managed', server_url=server_url, From 15cfe63268b45efbd9f32376d0edff30bf2670f7 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 19 Jan 2024 11:29:08 +0100 Subject: [PATCH 21/23] Copy & paste issue in rule integration test. --- tests/integration/targets/rule/tasks/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index 8029dab10..541edcb0e 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -62,11 +62,11 @@ existing_rule: "{{ lookup('checkmk.general.rules', ruleset=item.ruleset, comment_regex='Ansible managed', - server_url=server_url, + server_url=checkmk_var_server_url, site=outer_item.site, validate_certs=False, - automation_user=automation_user, - automation_secret=automation_secret) + automation_user=checkmk_var_automation_user, + automation_secret=checkmk_var_automation_secret) }}" delegate_to: localhost run_once: true # noqa run-once[task] From 05b4ef8cf4386f080d6d7f9c8cdc3cf04655fdfe Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 23 Jan 2024 09:03:45 +0100 Subject: [PATCH 22/23] Debugged the integration test for the rules module. --- tests/integration/targets/rule/tasks/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index 541edcb0e..f380dd7b0 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -55,9 +55,14 @@ rule: rule_id: "{{ existing_rule[0].id }}" properties: { - "description": "Modified this intentionally." + "comment": "{{ existing_rule[0].extensions.properties.comment }}", + "description": "Modified this intentionally.", + "disabled": "{{ existing_rule[0].extensions.properties.disabled }}" } + conditions: "{{ existing_rule[0].extensions.conditions }}" + value_raw: "{{ existing_rule[0].extensions.value_raw | string }}" state: "present" + when: "existing_rule|length>0" vars: existing_rule: "{{ lookup('checkmk.general.rules', ruleset=item.ruleset, From 30bf87a9bc65cccc4f3277748dfd8829c5d22c93 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 23 Jan 2024 16:32:09 +0100 Subject: [PATCH 23/23] Manually merge pull request #517 from meni2029/fix/module_rule_relative_id into the branch feature/module-rules-rule_id --- plugins/modules/rule.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 9971e0c46..a446c98a2 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -295,7 +295,7 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): return json.loads(response.read().decode("utf-8")).get("value") -def show_rule(module, base_url, headers, rule_id): +def get_rule_by_id(module, base_url, headers, rule_id): api_endpoint = "/objects/rule/" + rule_id url = "%s%s" % (base_url, api_endpoint) @@ -318,7 +318,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if module.params.get("state") == "absent": # When deleting and we already know the ID, don't compare return rule.get("rule_id") - rules = [show_rule(module, base_url, headers, rule.get("rule_id"))] + rules = [get_rule_by_id(module, base_url, headers, rule.get("rule_id"))] else: # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) @@ -327,6 +327,13 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if exc is not None: exit_failed(module, "value_raw in rule has invalid format") + # Get folder from neighbour rule if relative rule_id is given in location + if rule["location"]["rule_id"] is not None: + neighbour_rule = get_rule_by_id( + module, base_url, headers, rule["location"]["rule_id"] + ) + rule["folder"] = neighbour_rule["extensions"]["folder"] + if rules is not None: # Loop through all rules for r in rules: