diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 097acce4e..a446c98a2 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 @@ -284,20 +289,20 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): exit_failed( module, "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), + % (info["status"], str(info)), ) - return json.loads(response.read().decode("utf-8")) + return json.loads(response.read().decode("utf-8")).get("value") def get_rule_by_id(module, base_url, headers, rule_id): api_endpoint = "/objects/rule/" + rule_id - url = base_url + api_endpoint + url = "%s%s" % (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, " @@ -308,8 +313,15 @@ 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 + if module.params.get("state") == "absent": + # When deleting and we already know the ID, don't compare + return 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) (value_mod, exc) = safe_eval(rule["value_raw"], include_exceptions=True) if exc is not None: @@ -324,7 +336,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 +350,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 +359,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) @@ -380,12 +392,51 @@ 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 get_existing_rule(module, base_url, headers, ruleset, rule): + 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] + + 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 - 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 +522,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 +571,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") == "": + if not 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 not rule.get("rule_id"): + if not rule.get("properties"): + exit_failed(module, "Rule properties are required") + 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"): + 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") @@ -549,13 +602,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") diff --git a/tests/integration/targets/lookup_rules/tasks/test.yml b/tests/integration/targets/lookup_rules/tasks/test.yml index 675237540..9e1c17799 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=checkmk_var_server_url, site=outer_item.site, validate_certs=False, diff --git a/tests/integration/targets/rule/tasks/test.yml b/tests/integration/targets/rule/tasks/test.yml index b2f13c23c..f380dd7b0 100644 --- a/tests/integration/targets/rule/tasks/test.yml +++ b/tests/integration/targets/rule/tasks/test.yml @@ -45,6 +45,50 @@ 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: { + "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, + comment_regex='Ansible managed', + 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 + 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 }}"