From d1c28935b63b8696c3f178841e5d56b04a47e728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Wed, 14 Dec 2022 18:41:06 +0100 Subject: [PATCH 01/65] allow to specify the position of created rules --- plugins/modules/rule.py | 58 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 3d70dc891..e4db7fe80 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -199,6 +199,56 @@ def create_rule(module, base_url, headers, ruleset, rule): % (info["status"], info["body"]), ) + rule_id = json.loads(response.read().decode("utf-8"))["id"] + return rule_id + +def get_rule_etag(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]: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), + ) + print(" RULE ETAG : %s" % info["etag"]) + return info["etag"] + +def move_rule(module, base_url, headers, rule_id, position): + + # position parameter valid formats: + # + # position: { "position": "bottom_of_folder" } + # position: { "position": "top_of_folder" } + # position: { "position": "after_specific_rule", "rule_id" : string } + # position: { "position": "before_specific_rule", "rule_id" : string } + + if ( position.get("position") not in [ "bottom_of_folder", "top_of_folder", "after_specific_rule", "before_specific_rule" ] + or ( position.get("position") in [ "after_specific_rule", "before_specific_rule" ] + and ( position.get("rule_id") is None or position.get("rule_id") == "" ) + ) + ): exit_failed(module, "Position parameter format is not valid") + + api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" + + rule_etag = get_rule_etag(module, base_url, headers, rule_id) + headers["If-Match"] = rule_etag + + url = base_url + api_endpoint + + response, info = fetch_url( + module, url, module.jsonify(position), headers=headers, method="POST") + + if info["status"] not in [200, 204]: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), + ) def delete_rule(module, base_url, headers, rule_id): api_endpoint = "/objects/rule/" @@ -225,6 +275,7 @@ def run_module(): automation_secret=dict(type="str", required=True, no_log=True), ruleset=dict(type="str", required=True), rule=dict(type="dict", required=True), + position=dict(type="dict", required=False), state=dict(type="str", default="present", choices=["present", "absent"]), ) @@ -279,7 +330,12 @@ def run_module(): else: # If state is present, create the rule if module.params.get("state") == "present": - create_rule(module, base_url, headers, ruleset, rule) + rule_id = create_rule(module, base_url, headers, ruleset, rule) + # move the rule into position if specified + if module.params.get("position") is not None: + position = module.params.get("position") + position["folder"] = rule["folder"] + move_rule(module, base_url, headers, rule_id, position) exit_changed(module, "Created rule") else: # If state is absent, do nothing From ca98865173922ecc1d5f6a1d0452f909094dc5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Wed, 14 Dec 2022 19:14:41 +0100 Subject: [PATCH 02/65] cleanup code and add position param documentation --- plugins/modules/rule.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index e4db7fe80..c40fa6471 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -32,6 +32,14 @@ description: Name of the ruleset to manage. required: true type: str + position: + description: Position of the rule in the folder + required: false + type: dict + choices: [{"position":"bottom_of_folder"}, + {"position":"top_of_folder"}, + {"position":"after_specific_rule", "rule_id":string}, + {"position":"before_specific_rule", "rule_id":string}] state: description: State of the rule. choices: [present, absent] @@ -51,6 +59,7 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" + position: { "top_of_folder" } rule: conditions: { "host_labels": [], @@ -215,18 +224,10 @@ def get_rule_etag(module, base_url, headers, rule_id): "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) - print(" RULE ETAG : %s" % info["etag"]) return info["etag"] def move_rule(module, base_url, headers, rule_id, position): - # position parameter valid formats: - # - # position: { "position": "bottom_of_folder" } - # position: { "position": "top_of_folder" } - # position: { "position": "after_specific_rule", "rule_id" : string } - # position: { "position": "before_specific_rule", "rule_id" : string } - if ( position.get("position") not in [ "bottom_of_folder", "top_of_folder", "after_specific_rule", "before_specific_rule" ] or ( position.get("position") in [ "after_specific_rule", "before_specific_rule" ] and ( position.get("rule_id") is None or position.get("rule_id") == "" ) From 7a8b67355f51751592ff41921910ca43a1a8e86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 15 Dec 2022 13:28:16 +0100 Subject: [PATCH 03/65] fix code validation errors --- plugins/modules/rule.py | 42 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index c40fa6471..ca963837d 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -36,10 +36,11 @@ description: Position of the rule in the folder required: false type: dict - choices: [{"position":"bottom_of_folder"}, - {"position":"top_of_folder"}, - {"position":"after_specific_rule", "rule_id":string}, - {"position":"before_specific_rule", "rule_id":string}] + choices: + - {"position": "bottom_of_folder"} + - {"position": "top_of_folder"} + - {"position": "after_specific_rule", "rule_id": str} + - {"position": "before_specific_rule", "rule_id": str} state: description: State of the rule. choices: [present, absent] @@ -211,6 +212,7 @@ def create_rule(module, base_url, headers, ruleset, rule): rule_id = json.loads(response.read().decode("utf-8"))["id"] return rule_id + def get_rule_etag(module, base_url, headers, rule_id): api_endpoint = "/objects/rule/" + rule_id @@ -226,23 +228,35 @@ def get_rule_etag(module, base_url, headers, rule_id): ) return info["etag"] -def move_rule(module, base_url, headers, rule_id, position): - - if ( position.get("position") not in [ "bottom_of_folder", "top_of_folder", "after_specific_rule", "before_specific_rule" ] - or ( position.get("position") in [ "after_specific_rule", "before_specific_rule" ] - and ( position.get("rule_id") is None or position.get("rule_id") == "" ) - ) - ): exit_failed(module, "Position parameter format is not valid") +def move_rule(module, base_url, headers, rule_id, rule, position): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" + if ( + position.get("position") not in [ + "bottom_of_folder", + "top_of_folder", + "after_specific_rule", + "before_specific_rule", + ] or ( + position.get("position") in ["after_specific_rule", "before_specific_rule"] + and (position.get("rule_id") is None or position.get("rule_id") == "") + ) + ): + exit_failed(module, "Position parameter format is not valid") + rule_etag = get_rule_etag(module, base_url, headers, rule_id) headers["If-Match"] = rule_etag url = base_url + api_endpoint + # although we already have the rule id and an etag + # we still need to specify a folder + position["folder"] = rule.get("folder") + response, info = fetch_url( - module, url, module.jsonify(position), headers=headers, method="POST") + module, url, module.jsonify(position), headers=headers, method="POST" + ) if info["status"] not in [200, 204]: exit_failed( @@ -251,6 +265,7 @@ def move_rule(module, base_url, headers, rule_id, position): % (info["status"], info["body"]), ) + def delete_rule(module, base_url, headers, rule_id): api_endpoint = "/objects/rule/" @@ -335,8 +350,7 @@ def run_module(): # move the rule into position if specified if module.params.get("position") is not None: position = module.params.get("position") - position["folder"] = rule["folder"] - move_rule(module, base_url, headers, rule_id, position) + move_rule(module, base_url, headers, rule, rule_id, position) exit_changed(module, "Created rule") else: # If state is absent, do nothing From 64d6094193f16bc55a76fd4ac76d1ac0e05b16a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 15 Dec 2022 13:39:15 +0100 Subject: [PATCH 04/65] fix typo --- plugins/modules/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index ca963837d..b09e6fc2a 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -229,7 +229,7 @@ def get_rule_etag(module, base_url, headers, rule_id): return info["etag"] -def move_rule(module, base_url, headers, rule_id, rule, position): +def move_rule(module, base_url, headers, rule, rule_id, position): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" if ( From 61b733bbdf802879e950a7a5f2bf3a040b415a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 15 Dec 2022 13:50:33 +0100 Subject: [PATCH 05/65] fix code format again --- plugins/modules/rule.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index b09e6fc2a..727b64bac 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -232,16 +232,14 @@ def get_rule_etag(module, base_url, headers, rule_id): def move_rule(module, base_url, headers, rule, rule_id, position): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" - if ( - position.get("position") not in [ + if position.get("position") not in [ "bottom_of_folder", "top_of_folder", "after_specific_rule", "before_specific_rule", - ] or ( + ] or ( position.get("position") in ["after_specific_rule", "before_specific_rule"] and (position.get("rule_id") is None or position.get("rule_id") == "") - ) ): exit_failed(module, "Position parameter format is not valid") From 75369fc48cddd23225eaa3ea9bf54280f7cc6f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 15 Dec 2022 14:59:47 +0100 Subject: [PATCH 06/65] fixes for qa and sanity checks --- plugins/modules/rule.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 727b64bac..7dcc60991 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -36,11 +36,6 @@ description: Position of the rule in the folder required: false type: dict - choices: - - {"position": "bottom_of_folder"} - - {"position": "top_of_folder"} - - {"position": "after_specific_rule", "rule_id": str} - - {"position": "before_specific_rule", "rule_id": str} state: description: State of the rule. choices: [present, absent] @@ -233,13 +228,13 @@ def move_rule(module, base_url, headers, rule, rule_id, position): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" if position.get("position") not in [ - "bottom_of_folder", - "top_of_folder", - "after_specific_rule", - "before_specific_rule", + "bottom_of_folder", + "top_of_folder", + "after_specific_rule", + "before_specific_rule", ] or ( - position.get("position") in ["after_specific_rule", "before_specific_rule"] - and (position.get("rule_id") is None or position.get("rule_id") == "") + position.get("position") in ["after_specific_rule", "before_specific_rule"] + and (position.get("rule_id") is None or position.get("rule_id") == "") ): exit_failed(module, "Position parameter format is not valid") @@ -248,8 +243,6 @@ def move_rule(module, base_url, headers, rule, rule_id, position): url = base_url + api_endpoint - # although we already have the rule id and an etag - # we still need to specify a folder position["folder"] = rule.get("folder") response, info = fetch_url( From bfe679ed9a48e2a42a261da1e47bb69aa3b1a63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 15 Dec 2022 19:14:11 +0100 Subject: [PATCH 07/65] fix position parameter example --- plugins/modules/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 7dcc60991..0684bfed1 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -55,7 +55,7 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - position: { "top_of_folder" } + position: { "position": "top_of_folder" } rule: conditions: { "host_labels": [], From 93d3d4b8e433fad38761c5ebd47494dfe73e917d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Mon, 19 Dec 2022 13:03:08 +0100 Subject: [PATCH 08/65] make move_rule more generic, change position paremeter to "name" --- plugins/modules/rule.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 0684bfed1..f8083b8fd 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -55,7 +55,7 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - position: { "position": "top_of_folder" } + position: { "name": "top_of_folder" } rule: conditions: { "host_labels": [], @@ -224,27 +224,28 @@ def get_rule_etag(module, base_url, headers, rule_id): return info["etag"] -def move_rule(module, base_url, headers, rule, rule_id, position): +def move_rule(module, base_url, headers, rule_id, position): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" - if position.get("position") not in [ + if position.get("name") not in [ "bottom_of_folder", "top_of_folder", "after_specific_rule", "before_specific_rule", ] or ( - position.get("position") in ["after_specific_rule", "before_specific_rule"] + position.get("name") in ["after_specific_rule", "before_specific_rule"] and (position.get("rule_id") is None or position.get("rule_id") == "") ): - exit_failed(module, "Position parameter format is not valid") + exit_failed(module, "Position parameter mismatch") + + if position.get("folder") is None or position.get("folder") == "": + exit_failed(module, "Position folder parameter is missing") rule_etag = get_rule_etag(module, base_url, headers, rule_id) headers["If-Match"] = rule_etag url = base_url + api_endpoint - position["folder"] = rule.get("folder") - response, info = fetch_url( module, url, module.jsonify(position), headers=headers, method="POST" ) @@ -341,7 +342,8 @@ def run_module(): # move the rule into position if specified if module.params.get("position") is not None: position = module.params.get("position") - move_rule(module, base_url, headers, rule, rule_id, position) + position["folder"] = rule.get("folder") + move_rule(module, base_url, headers, rule_id, position) exit_changed(module, "Created rule") else: # If state is absent, do nothing From ca1772197890a936ac534bcaa11437049e636d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Mon, 19 Dec 2022 13:50:52 +0100 Subject: [PATCH 09/65] output response from the API --- plugins/modules/rule.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index f8083b8fd..adc90e577 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -109,10 +109,14 @@ RETURN = r""" msg: - description: The output message that the module generates. Contains the API response details in case of an error. + description: The output message that the module generates. Contains the API status details in case of an error. type: str returned: always sample: 'Rule created.' +content: + description: The Response body from the API when creating or moving a rule, or when a rule already exists. + type: dict + returned: when rule exists, is created or moved """ import json @@ -126,18 +130,18 @@ from urllib.parse import urlencode -def exit_failed(module, msg): - result = {"msg": msg, "changed": False, "failed": True} +def exit_failed(module, msg, content={}): + result = {"msg": msg, "content": content, "changed": False, "failed": True} module.fail_json(**result) -def exit_changed(module, msg): - result = {"msg": msg, "changed": True, "failed": False} +def exit_changed(module, msg, content={}): + result = {"msg": msg, "content": content, "changed": True, "failed": False} module.exit_json(**result) -def exit_ok(module, msg): - result = {"msg": msg, "changed": False, "failed": False} +def exit_ok(module, msg, content={}): + result = {"msg": msg, "content": content, "changed": False, "failed": False} module.exit_json(**result) @@ -176,7 +180,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) ): # If they are the same, return the ID - return r["id"] + return r return None @@ -204,8 +208,7 @@ def create_rule(module, base_url, headers, ruleset, rule): % (info["status"], info["body"]), ) - rule_id = json.loads(response.read().decode("utf-8"))["id"] - return rule_id + return json.loads(response.read().decode("utf-8")) def get_rule_etag(module, base_url, headers, rule_id): @@ -257,6 +260,8 @@ def move_rule(module, base_url, headers, rule_id, position): % (info["status"], info["body"]), ) + return json.loads(response.read().decode("utf-8")) + def delete_rule(module, base_url, headers, rule_id): api_endpoint = "/objects/rule/" @@ -323,8 +328,11 @@ def run_module(): "host_labels": [], "service_labels": [], } + # Get ID of rule that is the same as the given options - rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) + content = get_existing_rule(module, base_url, headers, ruleset, rule) + rule_id = content.get("id") + # If rule exists if rule_id is not None: # If state is absent, delete the rule @@ -333,18 +341,19 @@ def run_module(): exit_changed(module, "Deleted rule") # If state is present, do nothing else: - exit_ok(module, "Rule already exists") + exit_ok(module, "Rule already exists", content) # If rule does not exist else: # If state is present, create the rule if module.params.get("state") == "present": - rule_id = create_rule(module, base_url, headers, ruleset, rule) + content = create_rule(module, base_url, headers, ruleset, rule) # move the rule into position if specified if module.params.get("position") is not None: + rule_id = content.get("id") position = module.params.get("position") position["folder"] = rule.get("folder") - move_rule(module, base_url, headers, rule_id, position) - exit_changed(module, "Created rule") + content = move_rule(module, base_url, headers, rule_id, position) + exit_changed(module, "Created rule", content) else: # If state is absent, do nothing exit_ok(module, "Rule did not exist") From c29471a1b377fe4b8fd6364d1c2f179fe4dd9ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Mon, 19 Dec 2022 17:42:24 +0100 Subject: [PATCH 10/65] revert chage "position" to "name" --- plugins/modules/rule.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index adc90e577..070899e1c 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -55,7 +55,7 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - position: { "name": "top_of_folder" } + position: { "position": "top_of_folder" } rule: conditions: { "host_labels": [], @@ -230,13 +230,13 @@ def get_rule_etag(module, base_url, headers, rule_id): def move_rule(module, base_url, headers, rule_id, position): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" - if position.get("name") not in [ + if position.get("position") not in [ "bottom_of_folder", "top_of_folder", "after_specific_rule", "before_specific_rule", ] or ( - position.get("name") in ["after_specific_rule", "before_specific_rule"] + position.get("position") in ["after_specific_rule", "before_specific_rule"] and (position.get("rule_id") is None or position.get("rule_id") == "") ): exit_failed(module, "Position parameter mismatch") @@ -331,7 +331,10 @@ def run_module(): # Get ID of rule that is the same as the given options content = get_existing_rule(module, base_url, headers, ruleset, rule) - rule_id = content.get("id") + if content is not None: + rule_id = content.get("id") + else: + rule_id = None # If rule exists if rule_id is not None: From 4e599849b51e618420a0e1fef072055178096078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Mon, 19 Dec 2022 18:03:54 +0100 Subject: [PATCH 11/65] cleanup check on get_existing_rule return value --- plugins/modules/rule.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 070899e1c..027b32b11 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -331,13 +331,10 @@ def run_module(): # Get ID of rule that is the same as the given options content = get_existing_rule(module, base_url, headers, ruleset, rule) - if content is not None: - rule_id = content.get("id") - else: - rule_id = None # If rule exists - if rule_id is not None: + if content is not None: + rule_id = content.get("id") # If state is absent, delete the rule if module.params.get("state") == "absent": delete_rule(module, base_url, headers, rule_id) From 74db5fb8edacdfe2785ad9d45fd77561d815bafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Mon, 19 Dec 2022 18:32:12 +0100 Subject: [PATCH 12/65] improve documentation of the position parameter --- plugins/modules/rule.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 027b32b11..8bae23176 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -33,7 +33,15 @@ required: true type: str position: - description: Position of the rule in the folder + description: + - Position of the rule in the list. Valid formats: + + {"position": "top_of_folder"} + {"position": "bottom_of_folder"} + {"position": "before_specific_rule", rule_id: str} + {"position": "after_specific_rule", rule_id: str} + + The rule_id can be obtained from the output of a previously created rule. required: false type: dict state: @@ -76,6 +84,11 @@ } value_raw: "{'levels': (80.0, 90.0)}" state: "present" + register: response + +- name: Show the ID of the new rule + debug: + msg: "RULE ID : {{ response.content.id }}" # Delete first rule in this ruleset. - name: "Delete a rule." @@ -113,6 +126,7 @@ type: str returned: always sample: 'Rule created.' + content: description: The Response body from the API when creating or moving a rule, or when a rule already exists. type: dict @@ -179,7 +193,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) ): - # If they are the same, return the ID + # If they are the same, return the content return r return None From ca696201cfc0da11227e1ded226fbb1fd19d45ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Mon, 19 Dec 2022 22:55:43 +0100 Subject: [PATCH 13/65] fix documentation section --- plugins/modules/rule.py | 61 +++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 8bae23176..41e1fcebd 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -34,16 +34,24 @@ type: str position: description: - - Position of the rule in the list. Valid formats: - - {"position": "top_of_folder"} - {"position": "bottom_of_folder"} - {"position": "before_specific_rule", rule_id: str} - {"position": "after_specific_rule", rule_id: str} - - The rule_id can be obtained from the output of a previously created rule. + - Where to put the rule in the existing list of rules. + - By default rules are created at the bottom of the list. required: false type: dict + suboptions: + position: + description: Position in the list + required: true + type: str + choices: + - top_of_folder + - bottom_of_folder + - before_specific_rule + - after_specific_rule + rule_id: + description: + - Rule ID to use with before and after_specific_rule + type: str state: description: State of the rule. choices: [present, absent] @@ -56,6 +64,7 @@ EXAMPLES = r""" # Create a rule in checkgroup_parameters:memory_percentage_used. +# Put it at the top of the list - name: "Create a rule in checkgroup_parameters:memory_percentage_used." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -63,7 +72,7 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - position: { "position": "top_of_folder" } + position: {"position": "top_of_folder"} rule: conditions: { "host_labels": [], @@ -90,6 +99,40 @@ debug: msg: "RULE ID : {{ response.content.id }}" +# Create another rule in checkgroup_parameters:memory_percentage_used. +# Put it after the rule created above. +- name: "Create a rule in checkgroup_parameters:memory_percentage_used." + tribe29.checkmk.rule: + server_url: "http://localhost/" + site: "my_site" + automation_user: "automation" + automation_secret: "$SECRET" + ruleset: "checkgroup_parameters:memory_percentage_used" + position: { + "position": "after_specific_rule", + "rule_id": "{{ response.content.id }}" + } + rule: + conditions: { + "host_labels": [], + "host_name": { + "match_on": [ + "test2.tld" + ], + "operator": "one_of" + }, + "host_tags": [], + "service_labels": [] + } + properties: { + "comment": "Warning at 85%\nCritical at 99%\n", + "description": "Allow even higher memory usage", + "disabled": false, + "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" + } + value_raw: "{'levels': (85.0, 99.0)}" + state: "present" + # Delete first rule in this ruleset. - name: "Delete a rule." tribe29.checkmk.rule: From f08be1e9dd20b9be2a8e4d2576f82c11b804f513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sat, 24 Dec 2022 11:39:41 +0100 Subject: [PATCH 14/65] exit_changed when rule is moved --- plugins/modules/rule.py | 125 ++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 38 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 41e1fcebd..138321241 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -25,33 +25,47 @@ options: rule: - description: Definition of the rule as returned by the Checkmk API. - required: true + description: + - Definition of the rule as returned by the Checkmk API. + - Mutually exclusive with "rule_id". type: dict ruleset: - description: Name of the ruleset to manage. - required: true + description: + - Name of the ruleset to manage. + - Mutually exclusive with "rule_id". + - Required when "rule:" is used. type: str - position: + rule_id: + description: + - ID of an existing rule we want to move. + - Required if "rule:" is not used. + - Mutually exclusive with "rule_id". + type: str + move: description: - - Where to put the rule in the existing list of rules. + - Move the rule at the specified location. - By default rules are created at the bottom of the list. - required: false type: dict suboptions: position: - description: Position in the list + description: Position of the rule in the folder required: true type: str choices: - - top_of_folder - - bottom_of_folder - - before_specific_rule - - after_specific_rule + - "top" + - "bottom" + - "before" + - "after" rule_id: description: - - Rule ID to use with before and after_specific_rule + - Put the rule "before" or "after" this rule_id. + - Required when "position" is "before" or "after" type: str + folder: + description: + - Folder the rule should be moved to. + - the same folder is used. + - Required if other than "/", when "rule:" is not used. state: description: State of the rule. choices: [present, absent] @@ -72,7 +86,7 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - position: {"position": "top_of_folder"} + move: {"position": "top"} rule: conditions: { "host_labels": [], @@ -108,10 +122,9 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - position: { - "position": "after_specific_rule", - "rule_id": "{{ response.content.id }}" - } + move: + position: "after", + rule_id: "{{ response.content.id }}" rule: conditions: { "host_labels": [], @@ -132,8 +145,32 @@ } value_raw: "{'levels': (85.0, 99.0)}" state: "present" + register: content -# Delete first rule in this ruleset. +# TODO: Move a rule "123456789abcdef" to folder "test" after rule "987654321fedcba" +- name: "Move an existing rule at specified location." + tribe29.checkmk.rule: + server_url: "http://localhost/" + site: "my_site" + automation_user: "automation" + automation_secret: "$SECRET" + rule_id: "123456789abcdef" + move: + folder: "test" + position: "after" + rule_id: "987654321fedcba" + +# TODO: delete rule "123456789abcdef" +- name: "Delete a rule by ID." + tribe29.checkmk.rule: + server_url: "http://localhost/" + site: "my_site" + automation_user: "automation" + automation_secret: "$SECRET" + rule_id: "123456789abcdef" + state: absent + +# Delete the rule decribed. - name: "Delete a rule." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -284,30 +321,40 @@ def get_rule_etag(module, base_url, headers, rule_id): return info["etag"] -def move_rule(module, base_url, headers, rule_id, position): +def move_rule(module, base_url, headers, rule_id, move): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" - if position.get("position") not in [ - "bottom_of_folder", - "top_of_folder", - "after_specific_rule", - "before_specific_rule", - ] or ( - position.get("position") in ["after_specific_rule", "before_specific_rule"] - and (position.get("rule_id") is None or position.get("rule_id") == "") + if move.get("position") not in ["bottom", "top", "after", "before",] or ( + move.get("position") in ["after", "before"] + and (move.get("rule_id") is None or move.get("rule_id") == "") ): exit_failed(module, "Position parameter mismatch") - if position.get("folder") is None or position.get("folder") == "": + if move.get("folder") is None or move.get("folder") == "": exit_failed(module, "Position folder parameter is missing") + api_keywords = { + "top": "top_of_folder", + "bottom": "bottom_of_folder", + "before": "before_specific_rule", + "after": "after_specific_rule", + } + + params = { + "position": api_keywords.get(move.get("position")), + } + if move.get("position") in ["after", "before"]: + params["rule_id"] = move.get("rule_id") + else: + params["folder"] = move.get("folder") + rule_etag = get_rule_etag(module, base_url, headers, rule_id) headers["If-Match"] = rule_etag url = base_url + api_endpoint response, info = fetch_url( - module, url, module.jsonify(position), headers=headers, method="POST" + module, url, module.jsonify(params), headers=headers, method="POST" ) if info["status"] not in [200, 204]: @@ -345,7 +392,7 @@ def run_module(): automation_secret=dict(type="str", required=True, no_log=True), ruleset=dict(type="str", required=True), rule=dict(type="dict", required=True), - position=dict(type="dict", required=False), + move=dict(type="dict", required=False), state=dict(type="str", default="present", choices=["present", "absent"]), ) @@ -404,15 +451,17 @@ def run_module(): # If state is present, create the rule if module.params.get("state") == "present": content = create_rule(module, base_url, headers, ruleset, rule) - # move the rule into position if specified - if module.params.get("position") is not None: + # If specified, move rule + if module.params.get("move") is not None: rule_id = content.get("id") - position = module.params.get("position") - position["folder"] = rule.get("folder") - content = move_rule(module, base_url, headers, rule_id, position) - exit_changed(module, "Created rule", content) + move = module.params.get("move") + move["folder"] = rule.get("folder") + content = move_rule(module, base_url, headers, rule_id, move) + exit_changed(module, "Created and moved rule", content) + else: + exit_changed(module, "Created rule", content) + # If state is absent, do nothing else: - # If state is absent, do nothing exit_ok(module, "Rule did not exist") # Fallback From 70d3245b223f3628537f41a3788140f39108fa33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sat, 24 Dec 2022 11:51:46 +0100 Subject: [PATCH 15/65] fix sanity check: use None as default instead of dict --- plugins/modules/rule.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 138321241..113bec887 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -224,17 +224,17 @@ from urllib.parse import urlencode -def exit_failed(module, msg, content={}): +def exit_failed(module, msg, content=None): result = {"msg": msg, "content": content, "changed": False, "failed": True} module.fail_json(**result) -def exit_changed(module, msg, content={}): +def exit_changed(module, msg, content=None): result = {"msg": msg, "content": content, "changed": True, "failed": False} module.exit_json(**result) -def exit_ok(module, msg, content={}): +def exit_ok(module, msg, content=None): result = {"msg": msg, "content": content, "changed": False, "failed": False} module.exit_json(**result) From d3cd269996d3671195869e9977e96a1e34037335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sat, 24 Dec 2022 12:17:41 +0100 Subject: [PATCH 16/65] update documentation and examples --- plugins/modules/rule.py | 53 +++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 113bec887..be289e9ad 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -25,26 +25,16 @@ options: rule: - description: - - Definition of the rule as returned by the Checkmk API. - - Mutually exclusive with "rule_id". + description: Definition of the rule as returned by the Checkmk API. type: dict ruleset: - description: - - Name of the ruleset to manage. - - Mutually exclusive with "rule_id". - - Required when "rule:" is used. + description: Name of the ruleset to manage. type: str - rule_id: - description: - - ID of an existing rule we want to move. - - Required if "rule:" is not used. - - Mutually exclusive with "rule_id". - type: str move: description: - Move the rule at the specified location. - - By default rules are created at the bottom of the list. + - By default rules are created at the bottom of the folder. + - Mutually exclusive with I(state: absent) type: dict suboptions: position: @@ -58,14 +48,13 @@ - "after" rule_id: description: - - Put the rule "before" or "after" this rule_id. - - Required when "position" is "before" or "after" + - Put the created/moved rule C(before) or C(after) this rule_id. + - Required when I(position) is C(before) or C(after). + - Mutually exclusive with I(position: before) and I(after) type: str folder: description: - Folder the rule should be moved to. - - the same folder is used. - - Required if other than "/", when "rule:" is not used. state: description: State of the rule. choices: [present, absent] @@ -77,8 +66,8 @@ """ EXAMPLES = r""" -# Create a rule in checkgroup_parameters:memory_percentage_used. -# Put it at the top of the list +# Create a rule in checkgroup_parameters:memory_percentage_used +# and move it at the top of the folder. - name: "Create a rule in checkgroup_parameters:memory_percentage_used." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -86,7 +75,6 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - move: {"position": "top"} rule: conditions: { "host_labels": [], @@ -106,6 +94,8 @@ "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" } value_raw: "{'levels': (80.0, 90.0)}" + move: + position: "top" state: "present" register: response @@ -122,9 +112,6 @@ automation_user: "automation" automation_secret: "$SECRET" ruleset: "checkgroup_parameters:memory_percentage_used" - move: - position: "after", - rule_id: "{{ response.content.id }}" rule: conditions: { "host_labels": [], @@ -144,10 +131,13 @@ "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" } value_raw: "{'levels': (85.0, 99.0)}" + move: + position: "after" + rule_id: "{{ response.content.id }}" state: "present" register: content -# TODO: Move a rule "123456789abcdef" to folder "test" after rule "987654321fedcba" +# TODO: Move a rule "123456789abcdef" after rule "987654321fedcba" - name: "Move an existing rule at specified location." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -156,10 +146,21 @@ automation_secret: "$SECRET" rule_id: "123456789abcdef" move: - folder: "test" position: "after" rule_id: "987654321fedcba" +# TODO: Move rule "123456789abcdef" at the top of folder "~test" +- name: "Move an existing rule in another folder." + tribe29.checkmk.rule: + server_url: "http://localhost/" + site: "my_site" + automation_user: "automation" + automation_secret: "$SECRET" + rule_id: "123456789abcdef" + move: + position: "top" + folder: "~test" + # TODO: delete rule "123456789abcdef" - name: "Delete a rule by ID." tribe29.checkmk.rule: From 6b20fe68fe77f4417ec2c05e00e4b2a0bbdd9e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Tue, 3 Jan 2023 12:09:35 +0100 Subject: [PATCH 17/65] restore required for rule and ruleset --- plugins/modules/rule.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index be289e9ad..0898a41b9 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -26,9 +26,11 @@ options: rule: description: Definition of the rule as returned by the Checkmk API. + required: true type: dict ruleset: description: Name of the ruleset to manage. + required: true type: str move: description: From 4a307210ffbcaebcc6d863c3ade6008c6f58dfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Tue, 3 Jan 2023 15:11:54 +0100 Subject: [PATCH 18/65] fix documentation formatting --- plugins/modules/rule.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 0898a41b9..d3c70ecc2 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -36,11 +36,11 @@ description: - Move the rule at the specified location. - By default rules are created at the bottom of the folder. - - Mutually exclusive with I(state: absent) + - Mutually exclusive with state: absent. type: dict suboptions: position: - description: Position of the rule in the folder + description: Position of the rule in the folder. required: true type: str choices: @@ -50,9 +50,9 @@ - "after" rule_id: description: - - Put the created/moved rule C(before) or C(after) this rule_id. - - Required when I(position) is C(before) or C(after). - - Mutually exclusive with I(position: before) and I(after) + - Put the created/moved rule before or after this rule_id. + - Required when position is before or after. + - Mutually exclusive with position: before and after. type: str folder: description: From 9b0fbfc9650e2db2f498fd70e699698906aa836a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Tue, 3 Jan 2023 18:06:09 +0100 Subject: [PATCH 19/65] really fix doc format --- plugins/modules/rule.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index d3c70ecc2..b8a678511 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -36,11 +36,11 @@ description: - Move the rule at the specified location. - By default rules are created at the bottom of the folder. - - Mutually exclusive with state: absent. + - Mutually exclusive with I(state=absent). type: dict suboptions: position: - description: Position of the rule in the folder. + description: Position of the rule in the folder. required: true type: str choices: @@ -50,9 +50,9 @@ - "after" rule_id: description: - - Put the created/moved rule before or after this rule_id. - - Required when position is before or after. - - Mutually exclusive with position: before and after. + - Put the created/moved rule C(before) or C(after) this rule_id. + - Required when I(position) is C(before) or C(after). + - Mutually exclusive with I(position=before) and I(after). type: str folder: description: From 9481c513d1f0e24e9665ec9b2a70bc968cc6670a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Wed, 4 Jan 2023 10:43:04 +0100 Subject: [PATCH 20/65] remove trailing whitespace --- plugins/modules/rule.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index b8a678511..21ae7fcff 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -52,7 +52,7 @@ description: - Put the created/moved rule C(before) or C(after) this rule_id. - Required when I(position) is C(before) or C(after). - - Mutually exclusive with I(position=before) and I(after). + - Mutually exclusive with I(position=before) and I(after). type: str folder: description: @@ -96,7 +96,7 @@ "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" } value_raw: "{'levels': (80.0, 90.0)}" - move: + move: position: "top" state: "present" register: response @@ -163,7 +163,7 @@ position: "top" folder: "~test" -# TODO: delete rule "123456789abcdef" +# TODO: delete rule "123456789abcdef" - name: "Delete a rule by ID." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -327,7 +327,7 @@ def get_rule_etag(module, base_url, headers, rule_id): def move_rule(module, base_url, headers, rule_id, move): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" - if move.get("position") not in ["bottom", "top", "after", "before",] or ( + if move.get("position") not in ["bottom", "top", "after", "before"] or ( move.get("position") in ["after", "before"] and (move.get("rule_id") is None or move.get("rule_id") == "") ): From de2483eb1bc79bcdb4db98f1e22e18c39e311327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Wed, 4 Jan 2023 15:49:08 +0100 Subject: [PATCH 21/65] return only the rule ID, not full response --- plugins/modules/rule.py | 72 ++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 21ae7fcff..83cb42603 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -103,10 +103,10 @@ - name: Show the ID of the new rule debug: - msg: "RULE ID : {{ response.content.id }}" + msg: "RULE ID : {{ response.id }}" -# Create another rule in checkgroup_parameters:memory_percentage_used. -# Put it after the rule created above. +# Create another rule in checkgroup_parameters:memory_percentage_used +# and put it after the rule created above. - name: "Create a rule in checkgroup_parameters:memory_percentage_used." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -135,42 +135,43 @@ value_raw: "{'levels': (85.0, 99.0)}" move: position: "after" - rule_id: "{{ response.content.id }}" + rule_id: "{{ response.id }}" state: "present" - register: content -# TODO: Move a rule "123456789abcdef" after rule "987654321fedcba" +# TODO: Move rule "5edbdf02-325e-4101-a132-fcb4ee6835a4" +# after rule "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" - name: "Move an existing rule at specified location." tribe29.checkmk.rule: server_url: "http://localhost/" site: "my_site" automation_user: "automation" automation_secret: "$SECRET" - rule_id: "123456789abcdef" + rule_id: "5edbdf02-325e-4101-a132-fcb4ee6835a4" move: position: "after" - rule_id: "987654321fedcba" + rule_id: "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" -# TODO: Move rule "123456789abcdef" at the top of folder "~test" +# TODO: Move rule "5edbdf02-325e-4101-a132-fcb4ee6835a4" +# at the top of folder "~test" - name: "Move an existing rule in another folder." tribe29.checkmk.rule: server_url: "http://localhost/" site: "my_site" automation_user: "automation" automation_secret: "$SECRET" - rule_id: "123456789abcdef" + rule_id: "5edbdf02-325e-4101-a132-fcb4ee6835a4" move: position: "top" folder: "~test" -# TODO: delete rule "123456789abcdef" +# TODO: delete rule "5edbdf02-325e-4101-a132-fcb4ee6835a4" - name: "Delete a rule by ID." tribe29.checkmk.rule: server_url: "http://localhost/" site: "my_site" automation_user: "automation" automation_secret: "$SECRET" - rule_id: "123456789abcdef" + rule_id: "5edbdf02-325e-4101-a132-fcb4ee6835a4" state: absent # Delete the rule decribed. @@ -210,10 +211,11 @@ returned: always sample: 'Rule created.' -content: - description: The Response body from the API when creating or moving a rule, or when a rule already exists. - type: dict +id: + description: The ID of the rule, when it is created or when it already exists. + type: str returned: when rule exists, is created or moved + sample: "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" """ import json @@ -227,18 +229,18 @@ from urllib.parse import urlencode -def exit_failed(module, msg, content=None): - result = {"msg": msg, "content": content, "changed": False, "failed": True} +def exit_failed(module, msg, id=""): + result = {"msg": msg, "id": id, "changed": False, "failed": True} module.fail_json(**result) -def exit_changed(module, msg, content=None): - result = {"msg": msg, "content": content, "changed": True, "failed": False} +def exit_changed(module, msg, id=""): + result = {"msg": msg, "id": id, "changed": True, "failed": False} module.exit_json(**result) -def exit_ok(module, msg, content=None): - result = {"msg": msg, "content": content, "changed": False, "failed": False} +def exit_ok(module, msg, id=""): + result = {"msg": msg, "id": id, "changed": False, "failed": False} module.exit_json(**result) @@ -276,8 +278,8 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) ): - # If they are the same, return the content - return r + # If they are the same, return the ID + return r["id"] return None @@ -305,7 +307,9 @@ def create_rule(module, base_url, headers, ruleset, rule): % (info["status"], info["body"]), ) - return json.loads(response.read().decode("utf-8")) + r = json.loads(response.read().decode("utf-8")) + + return r["id"] def get_rule_etag(module, base_url, headers, rule_id): @@ -367,7 +371,9 @@ def move_rule(module, base_url, headers, rule_id, move): % (info["status"], info["body"]), ) - return json.loads(response.read().decode("utf-8")) + r = json.loads(response.read().decode("utf-8")) + + return r["id"] def delete_rule(module, base_url, headers, rule_id): @@ -437,32 +443,30 @@ def run_module(): } # Get ID of rule that is the same as the given options - content = get_existing_rule(module, base_url, headers, ruleset, rule) + rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) # If rule exists - if content is not None: - rule_id = content.get("id") + if rule_id is not None: # If state is absent, delete the rule if module.params.get("state") == "absent": delete_rule(module, base_url, headers, rule_id) exit_changed(module, "Deleted rule") # If state is present, do nothing else: - exit_ok(module, "Rule already exists", content) + exit_ok(module, "Rule already exists", rule_id) # If rule does not exist else: # If state is present, create the rule if module.params.get("state") == "present": - content = create_rule(module, base_url, headers, ruleset, rule) + rule_id = create_rule(module, base_url, headers, ruleset, rule) # If specified, move rule if module.params.get("move") is not None: - rule_id = content.get("id") move = module.params.get("move") move["folder"] = rule.get("folder") - content = move_rule(module, base_url, headers, rule_id, move) - exit_changed(module, "Created and moved rule", content) + rule_id = move_rule(module, base_url, headers, rule_id, move) + exit_changed(module, "Created and moved rule", rule_id) else: - exit_changed(module, "Created rule", content) + exit_changed(module, "Created rule", rule_id) # If state is absent, do nothing else: exit_ok(module, "Rule did not exist") From 6847bc7a00b3ad08e8cb254885d2d3183597ab16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 8 Jan 2023 15:00:52 +0100 Subject: [PATCH 22/65] rework move param checking --- plugins/modules/rule.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 83cb42603..57ce9459a 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -57,6 +57,7 @@ folder: description: - Folder the rule should be moved to. + - Mutually exclusive with I(state=present). state: description: State of the rule. choices: [present, absent] @@ -331,15 +332,6 @@ def get_rule_etag(module, base_url, headers, rule_id): def move_rule(module, base_url, headers, rule_id, move): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" - if move.get("position") not in ["bottom", "top", "after", "before"] or ( - move.get("position") in ["after", "before"] - and (move.get("rule_id") is None or move.get("rule_id") == "") - ): - exit_failed(module, "Position parameter mismatch") - - if move.get("folder") is None or move.get("folder") == "": - exit_failed(module, "Position folder parameter is missing") - api_keywords = { "top": "top_of_folder", "bottom": "bottom_of_folder", @@ -442,6 +434,28 @@ def run_module(): "service_labels": [], } + # Check if the move param is correct and set defaults + move = module.params.get("move") + if move is not None: + if module.params.get("state") == "absent": + exit_failed(module, "Param 'move' is invalid when 'state=absent'") + if move.get("position") not in ["bottom", "top", "after", "before"]: + exit_failed(module, "Unknown position keyword") + if move.get("position") in ["after", "before"]: + if move.get("rule_id") is None or move.get("rule_id") == "": + exit_failed(module, "Position requires 'rule_id'") + if move.get("folder") is not None: + exit_failed(module, "Position does not support 'folder'") + if module.params.get("state") == "present": + if move.get("position") in ["top", "bottom"]: + if move.get("folder") is not None: + exit_failed( + module, "Position 'folder' is not valid with 'state=present'" + ) + move["folder"] = rule.get("folder") + if move.get("rule_id") is not None: + exit_failed(module, "Position does not support 'rule_id'") + # Get ID of rule that is the same as the given options rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) @@ -460,13 +474,9 @@ def run_module(): if module.params.get("state") == "present": rule_id = create_rule(module, base_url, headers, ruleset, rule) # If specified, move rule - if module.params.get("move") is not None: - move = module.params.get("move") - move["folder"] = rule.get("folder") + if move is not None: rule_id = move_rule(module, base_url, headers, rule_id, move) - exit_changed(module, "Created and moved rule", rule_id) - else: - exit_changed(module, "Created rule", rule_id) + exit_changed(module, "Created rule", rule_id) # If state is absent, do nothing else: exit_ok(module, "Rule did not exist") From babe5f68ae8f24bac4bf3ec40b38437cd5dba7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 8 Jan 2023 15:01:51 +0100 Subject: [PATCH 23/65] remove trailing white space --- plugins/modules/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 57ce9459a..be70b6959 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -57,7 +57,7 @@ folder: description: - Folder the rule should be moved to. - - Mutually exclusive with I(state=present). + - Mutually exclusive with I(state=present). state: description: State of the rule. choices: [present, absent] From dd3d868b967d43fb318db18320bbc943b5923bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 8 Jan 2023 15:06:04 +0100 Subject: [PATCH 24/65] fix indentation --- plugins/modules/rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index be70b6959..fe14a0aaf 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -453,8 +453,8 @@ def run_module(): module, "Position 'folder' is not valid with 'state=present'" ) move["folder"] = rule.get("folder") - if move.get("rule_id") is not None: - exit_failed(module, "Position does not support 'rule_id'") + if move.get("rule_id") is not None: + exit_failed(module, "Position does not support 'rule_id'") # Get ID of rule that is the same as the given options rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) From 48ed1365ca08fe2ec934f93172643c1dc88e86f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 8 Jan 2023 15:16:03 +0100 Subject: [PATCH 25/65] add comments for move["folder"] == rule["folder"] --- plugins/modules/rule.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index fe14a0aaf..5e282f1f1 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -448,6 +448,7 @@ def run_module(): exit_failed(module, "Position does not support 'folder'") if module.params.get("state") == "present": if move.get("position") in ["top", "bottom"]: + # always move the rule within the creation folder if move.get("folder") is not None: exit_failed( module, "Position 'folder' is not valid with 'state=present'" From 950adc33802b65e200b4ce047e10800b3b51f40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 8 Jan 2023 15:19:21 +0100 Subject: [PATCH 26/65] make get_existing_rule also compare the rule folder --- plugins/modules/rule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 5e282f1f1..7bc2c0229 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -275,7 +275,8 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): for r in rules.get("value"): # Check if conditions, properties and values are the same if ( - sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) + r["folder"] == rule["folder"] + and sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) ): From 11bd7a39c27b39357cf8fdc97c28d508259028ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 8 Jan 2023 21:24:10 +0100 Subject: [PATCH 27/65] Revert "make get_existing_rule also compare the rule folder" This reverts commit 950adc33802b65e200b4ce047e10800b3b51f40d. --- plugins/modules/rule.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 7bc2c0229..5e282f1f1 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -275,8 +275,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): for r in rules.get("value"): # Check if conditions, properties and values are the same if ( - r["folder"] == rule["folder"] - and sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) + sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) ): From b40c380844550ad6afb4d738bfa408076ce60540 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 11 Jan 2023 10:39:12 +0100 Subject: [PATCH 28/65] WIP: Enable agent role to register on remote site. --- roles/agent/README.md | 8 ++++++++ roles/agent/defaults/main.yml | 4 +++- roles/agent/tasks/main.yml | 8 ++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/roles/agent/README.md b/roles/agent/README.md index 1b9a53f30..614bc373f 100644 --- a/roles/agent/README.md +++ b/roles/agent/README.md @@ -46,6 +46,14 @@ The port of the web interface of your Checkmk server. Defaults to port 80 for ht The name of your Checkmk site. + checkmk_agent_registration_server: "{{ checkmk_agent_server }}" + +The server you want to use for registration tasks (Agent updates and TLS encryption). Defaults to {{ checkmk_agent_server }}. + + checkmk_agent_registration_site: "{{ checkmk_agent_site }}" + +The server you want to use for registration tasks (Agent updates and TLS encryption). Defaults to {{ checkmk_agent_site }}. + checkmk_agent_user: automation The user used to authenticate against your Checkmk site. diff --git a/roles/agent/defaults/main.yml b/roles/agent/defaults/main.yml index b78b3e2ca..c7c74971f 100644 --- a/roles/agent/defaults/main.yml +++ b/roles/agent/defaults/main.yml @@ -3,9 +3,11 @@ checkmk_agent_version: "2.1.0p13" checkmk_agent_edition: cre checkmk_agent_protocol: http checkmk_agent_server: localhost +checkmk_agent_site: my_site +checkmk_agent_registration_server: "{{ checkmk_agent_server }}" +checkmk_agent_registration_site: "{{ checkmk_agent_site }}" checkmk_agent_server_validate_certs: 'true' checkmk_agent_port: "{% if checkmk_agent_protocol == 'https' %}443{% else %}80{% endif %}" -checkmk_agent_site: my_site checkmk_agent_user: "{{ automation_user | default('automation') }}" # Depending on which user you will be using, set the password or secret: diff --git a/roles/agent/tasks/main.yml b/roles/agent/tasks/main.yml index ae5276524..85f94a74e 100644 --- a/roles/agent/tasks/main.yml +++ b/roles/agent/tasks/main.yml @@ -47,7 +47,7 @@ - name: "Create host on server." tribe29.checkmk.host: - server_url: "{{ checkmk_agent_protocol }}://{{ checkmk_agent_server }}:{{ checkmk_agent_port }}/" + server_url: "{{ checkmk_agent_protocol }}://{{ checkmk_agent_registration_server }}:{{ checkmk_agent_port }}/" site: "{{ checkmk_agent_site }}" validate_certs: "{{ checkmk_agent_server_validate_certs | bool }}" automation_user: "{{ checkmk_agent_user }}" @@ -78,7 +78,7 @@ become: true ansible.builtin.shell: | cmk-update-agent register -H {{ checkmk_agent_host_name }} \ - -s {{ checkmk_agent_server }} -i {{ checkmk_agent_site }} -p {{ checkmk_agent_protocol }} \ + -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_site }} -p {{ checkmk_agent_protocol }} \ -U {{ checkmk_agent_user }} -P {{ checkmk_agent_auth }} register: checkmk_agent_update_state when: | @@ -94,7 +94,7 @@ become: true ansible.builtin.shell: | cmk-update-agent register -H {{ checkmk_agent_host_name }} \ - -s {{ checkmk_agent_server }} -i {{ checkmk_agent_site }} -p {{ checkmk_agent_protocol }} \ + -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_site }} -p {{ checkmk_agent_protocol }} \ -U {{ checkmk_agent_user }} -S {{ checkmk_agent_auth }} register: checkmk_agent_update_state when: | @@ -109,7 +109,7 @@ become: true ansible.builtin.shell: | cmk-agent-ctl register -H {{ checkmk_agent_host_name }} \ - -s {{ checkmk_agent_server }} -i {{ checkmk_agent_site }} \ + -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_site }} \ -U {{ checkmk_agent_user }} -P {{ checkmk_agent_auth }} --trust-cert register: checkmk_agent_tls_state when: | From 19bc24dcce2a99b02f0a158c387d42d631c80d46 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 11 Jan 2023 11:17:09 +0100 Subject: [PATCH 29/65] Update python requirements. --- Vagrantfile | 2 +- requirements.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 314f05dd9..818cf19d1 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -31,7 +31,6 @@ Vagrant.configure("2") do |config| pip install -r /vagrant/requirements.txt sudo -u vagrant ansible-galaxy collection install -f -r /vagrant/requirements.yml mkdir -p /home/vagrant/ansible_collections/tribe29/checkmk - rsync -avzh /vagrant/* /home/vagrant/ansible_collections/tribe29/checkmk/ mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null @@ -40,6 +39,7 @@ Vagrant.configure("2") do |config| usermod -aG docker vagrant SCRIPT srv.vm.provision "shell", inline: $script + srv.vm.synced_folder "./", "/home/vagrant/ansible_collections/tribe29/checkmk/" end # Ubuntu diff --git a/requirements.txt b/requirements.txt index e434c1bc0..3f185d458 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -ansible >= 4.10.0 -antsibull-changelog >= 0.12.0 -antsibull-docs >= 1.1.0 -ansible-lint >= 5.4.0 +ansible >= 7.0.0 +antsibull-changelog >= 0.16.0 +antsibull-docs >= 1.5.0 +ansible-lint >= 6.0.0 jinja2 >= 3.0.0 molecule >= 3.5.0 molecule-docker >= 1.1.0 \ No newline at end of file From 265ecd529b631cf443614bdd11cc7db289772008 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Wed, 11 Jan 2023 12:05:56 +0100 Subject: [PATCH 30/65] Prepare next release. Bump Checkmk version in tests and increase collection version. --- SUPPORT.md | 1 + galaxy.yml | 2 +- requirements.yml | 2 +- roles/server/molecule/2.1.0/group_vars/all.yml | 2 +- tests/integration/targets/activation/vars/main.yml | 2 +- tests/integration/targets/contact_group/vars/main.yml | 2 +- tests/integration/targets/discovery/vars/main.yml | 2 +- tests/integration/targets/downtime/vars/main.yml | 2 +- tests/integration/targets/folder/vars/main.yml | 2 +- tests/integration/targets/host/vars/main.yml | 2 +- tests/integration/targets/host_group/vars/main.yml | 2 +- tests/integration/targets/rule/vars/main.yml | 2 +- tests/integration/targets/service_group/vars/main.yml | 2 +- tests/integration/targets/tag_group/vars/main.yml | 2 +- 14 files changed, 14 insertions(+), 13 deletions(-) diff --git a/SUPPORT.md b/SUPPORT.md index e747be2a5..990c636b4 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -17,3 +17,4 @@ Collection Version | Checkmk Versions | Remarks 0.13.0 | 2.1.0p17, 2.0.0p31 | None 0.14.0 | 2.1.0p17, 2.0.0p31 | None 0.15.0 | 2.1.0p18, 2.0.0p32 | None +0.16.0 | 2.1.0p19, 2.0.0p32 | None \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml index 375a67625..f0c5de529 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -10,7 +10,7 @@ name: checkmk # The version of the collection. Must be compatible with semantic versioning -version: 0.15.0 +version: 0.16.0 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/requirements.yml b/requirements.yml index ecdbcc2ac..fd34ec0cf 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,4 +1,4 @@ --- collections: - name: tribe29.checkmk - version: 0.15.0 + version: 0.16.0 diff --git a/roles/server/molecule/2.1.0/group_vars/all.yml b/roles/server/molecule/2.1.0/group_vars/all.yml index a7d672cf8..f3f5726c0 100644 --- a/roles/server/molecule/2.1.0/group_vars/all.yml +++ b/roles/server/molecule/2.1.0/group_vars/all.yml @@ -1,6 +1,6 @@ --- # General -checkmk_version: "2.1.0p18" +checkmk_version: "2.1.0p19" checkmk_edition: "cre" checkmk_site: "stable" server_url: "http://127.0.0.1/" diff --git a/tests/integration/targets/activation/vars/main.yml b/tests/integration/targets/activation/vars/main.yml index afc7857af..3c92bd28e 100644 --- a/tests/integration/targets/activation/vars/main.yml +++ b/tests/integration/targets/activation/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/contact_group/vars/main.yml b/tests/integration/targets/contact_group/vars/main.yml index d4914711b..c6ef0d557 100644 --- a/tests/integration/targets/contact_group/vars/main.yml +++ b/tests/integration/targets/contact_group/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/discovery/vars/main.yml b/tests/integration/targets/discovery/vars/main.yml index afc7857af..3c92bd28e 100644 --- a/tests/integration/targets/discovery/vars/main.yml +++ b/tests/integration/targets/discovery/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/downtime/vars/main.yml b/tests/integration/targets/downtime/vars/main.yml index afc7857af..3c92bd28e 100644 --- a/tests/integration/targets/downtime/vars/main.yml +++ b/tests/integration/targets/downtime/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/folder/vars/main.yml b/tests/integration/targets/folder/vars/main.yml index 92bc95002..dd8b82a04 100644 --- a/tests/integration/targets/folder/vars/main.yml +++ b/tests/integration/targets/folder/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/host/vars/main.yml b/tests/integration/targets/host/vars/main.yml index afc7857af..3c92bd28e 100644 --- a/tests/integration/targets/host/vars/main.yml +++ b/tests/integration/targets/host/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/host_group/vars/main.yml b/tests/integration/targets/host_group/vars/main.yml index 49a26d44d..4b2c12952 100644 --- a/tests/integration/targets/host_group/vars/main.yml +++ b/tests/integration/targets/host_group/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/rule/vars/main.yml b/tests/integration/targets/rule/vars/main.yml index 7f8086ff5..dd3403583 100644 --- a/tests/integration/targets/rule/vars/main.yml +++ b/tests/integration/targets/rule/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" download_url: "https://download.checkmk.com/checkmk/{{ item.version }}/check-mk-raw-{{ item.version }}_0.{{ ansible_distribution_release }}_amd64.deb" site: "test" diff --git a/tests/integration/targets/service_group/vars/main.yml b/tests/integration/targets/service_group/vars/main.yml index 8b8ce543b..01cd7101c 100644 --- a/tests/integration/targets/service_group/vars/main.yml +++ b/tests/integration/targets/service_group/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" diff --git a/tests/integration/targets/tag_group/vars/main.yml b/tests/integration/targets/tag_group/vars/main.yml index 414fac7eb..7c8f1166c 100644 --- a/tests/integration/targets/tag_group/vars/main.yml +++ b/tests/integration/targets/tag_group/vars/main.yml @@ -1,6 +1,6 @@ --- checkmk_versions: - - version: "2.1.0p18" + - version: "2.1.0p19" site: "stable" - version: "2.0.0p32" site: "oldstable" From 559440d3274f4e0ace1270870e66c18f72072d7c Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Fri, 13 Jan 2023 08:38:37 +0100 Subject: [PATCH 31/65] Update collection tags. --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index f0c5de529..4ce5c00d3 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -37,7 +37,7 @@ license_file: LICENSE # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: [tribe29, checkmk, monitoring, check_mk, check, discovery] +tags: [tribe29, checkmk, monitoring, check_mk, check, discovery, ubuntu, debian, sles, rhel] # Collections that this collection requires to be installed for it to be usable. The key of the dict is the # collection label 'namespace.name'. The value is a version range From 9511016f9301b0e851da3f8a79255a5d0e2ad55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sat, 14 Jan 2023 14:05:11 +0100 Subject: [PATCH 32/65] use "location" instead of "move" and move the param under "rule" --- plugins/modules/rule.py | 205 +++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 106 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 5e282f1f1..97896a99c 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -28,36 +28,55 @@ description: Definition of the rule as returned by the Checkmk API. required: true type: dict - ruleset: - description: Name of the ruleset to manage. - required: true - type: str - move: - description: - - Move the rule at the specified location. - - By default rules are created at the bottom of the folder. - - Mutually exclusive with I(state=absent). - type: dict suboptions: - position: - description: Position of the rule in the folder. - required: true - type: str - choices: - - "top" - - "bottom" - - "before" - - "after" - rule_id: + location: description: - - Put the created/moved rule C(before) or C(after) this rule_id. - - Required when I(position) is C(before) or C(after). - - Mutually exclusive with I(position=before) and I(after). - type: str + - Location of the rule within a folder. + - By default rules are created at the bottom of the "/" folder. + - Has no effect when I(state=absent). + type: dict + suboptions: + position: + description: Position of the rule in the folder. + type: str + choices: + - "top" + - "bottom" + - "before" + - "after" + default: "bottom" + rule_id: + description: + - Put the rule C(before) or C(after) this rule_id. + - Required when I(position) is C(before) or C(after). + - Mutually exclusive with I(position=top) and I(bottom). + type: str + folder: + description: + - Put the rule at the C(top) or C(bottom) of this folder. + - Required when I(position) is C(top) or C(bottom). + - Mutually exclusive with I(position=before) and I(after). + default: "/" + type: str folder: description: - - Folder the rule should be moved to. - - Mutually exclusive with I(state=present). + - Folder the rule should belong to. + - Deprecated, use I(location) instead. + - Mutually exclusive with I(location). + type: str + conditions: + description: Conditions of the rule. + type: dict + properties: + description: Properties of the rule. + type: dict + value_raw: + description: Rule values as exported from the UI. + type: str + ruleset: + description: Name of the ruleset to manage. + required: true + type: str state: description: State of the rule. choices: [present, absent] @@ -70,7 +89,7 @@ EXAMPLES = r""" # Create a rule in checkgroup_parameters:memory_percentage_used -# and move it at the top of the folder. +# at the top of the main folder. - name: "Create a rule in checkgroup_parameters:memory_percentage_used." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -97,8 +116,9 @@ "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" } value_raw: "{'levels': (80.0, 90.0)}" - move: - position: "top" + location: + folder: "/" + position: "top" state: "present" register: response @@ -134,48 +154,12 @@ "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" } value_raw: "{'levels': (85.0, 99.0)}" - move: - position: "after" - rule_id: "{{ response.id }}" + location: + position: "after" + rule_id: "{{ response.id }}" state: "present" -# TODO: Move rule "5edbdf02-325e-4101-a132-fcb4ee6835a4" -# after rule "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" -- name: "Move an existing rule at specified location." - tribe29.checkmk.rule: - server_url: "http://localhost/" - site: "my_site" - automation_user: "automation" - automation_secret: "$SECRET" - rule_id: "5edbdf02-325e-4101-a132-fcb4ee6835a4" - move: - position: "after" - rule_id: "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" - -# TODO: Move rule "5edbdf02-325e-4101-a132-fcb4ee6835a4" -# at the top of folder "~test" -- name: "Move an existing rule in another folder." - tribe29.checkmk.rule: - server_url: "http://localhost/" - site: "my_site" - automation_user: "automation" - automation_secret: "$SECRET" - rule_id: "5edbdf02-325e-4101-a132-fcb4ee6835a4" - move: - position: "top" - folder: "~test" - -# TODO: delete rule "5edbdf02-325e-4101-a132-fcb4ee6835a4" -- name: "Delete a rule by ID." - tribe29.checkmk.rule: - server_url: "http://localhost/" - site: "my_site" - automation_user: "automation" - automation_secret: "$SECRET" - rule_id: "5edbdf02-325e-4101-a132-fcb4ee6835a4" - state: absent - -# Delete the rule decribed. +# Delete the rule described. - name: "Delete a rule." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -215,7 +199,7 @@ id: description: The ID of the rule, when it is created or when it already exists. type: str - returned: when rule exists, is created or moved + returned: when rule exists, is created sample: "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" """ @@ -329,7 +313,7 @@ def get_rule_etag(module, base_url, headers, rule_id): return info["etag"] -def move_rule(module, base_url, headers, rule_id, move): +def move_rule(module, base_url, headers, rule_id, location): api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" api_keywords = { @@ -340,15 +324,14 @@ def move_rule(module, base_url, headers, rule_id, move): } params = { - "position": api_keywords.get(move.get("position")), + "position": api_keywords[location["position"]], } - if move.get("position") in ["after", "before"]: - params["rule_id"] = move.get("rule_id") + if location["position"] in ["after", "before"]: + params["rule_id"] = location["rule_id"] else: - params["folder"] = move.get("folder") + params["folder"] = location["folder"] - rule_etag = get_rule_etag(module, base_url, headers, rule_id) - headers["If-Match"] = rule_etag + headers["If-Match"] = get_rule_etag(module, base_url, headers, rule_id) url = base_url + api_endpoint @@ -392,8 +375,40 @@ def run_module(): automation_user=dict(type="str", required=True), automation_secret=dict(type="str", required=True, no_log=True), ruleset=dict(type="str", required=True), - rule=dict(type="dict", required=True), - move=dict(type="dict", required=False), + rule=dict( + type="dict", + required=True, + options=dict( + folder=dict(type="str"), + conditions=dict(type="dict"), + properties=dict(type="dict"), + value_raw=dict(type="str"), + location=dict( + type="dict", + options=dict( + position=dict( + type="str", + choices=["top", "bottom", "before", "after"], + default="bottom", + ), + folder=dict( + type="str", + default="/", + ), + rule_id=dict(type="str"), + ), + required_if=[ + ("position", "top", ("folder",)), + ("position", "bottom", ("folder",)), + ("position", "before", ("rule_id",)), + ("position", "after", ("rule_id",)), + ], + mutually_exclusive=[("folder", "rule_id")], + apply_defaults=True, + ), + ), + mutually_exclusive=[("folder", "location")], + ), state=dict(type="str", default="present", choices=["present", "absent"]), ) @@ -418,10 +433,11 @@ def run_module(): # Get the variables ruleset = module.params.get("ruleset", "") 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"] = "/" + 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") == "": @@ -434,29 +450,6 @@ def run_module(): "service_labels": [], } - # Check if the move param is correct and set defaults - move = module.params.get("move") - if move is not None: - if module.params.get("state") == "absent": - exit_failed(module, "Param 'move' is invalid when 'state=absent'") - if move.get("position") not in ["bottom", "top", "after", "before"]: - exit_failed(module, "Unknown position keyword") - if move.get("position") in ["after", "before"]: - if move.get("rule_id") is None or move.get("rule_id") == "": - exit_failed(module, "Position requires 'rule_id'") - if move.get("folder") is not None: - exit_failed(module, "Position does not support 'folder'") - if module.params.get("state") == "present": - if move.get("position") in ["top", "bottom"]: - # always move the rule within the creation folder - if move.get("folder") is not None: - exit_failed( - module, "Position 'folder' is not valid with 'state=present'" - ) - move["folder"] = rule.get("folder") - if move.get("rule_id") is not None: - exit_failed(module, "Position does not support 'rule_id'") - # Get ID of rule that is the same as the given options rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) @@ -474,9 +467,9 @@ def run_module(): # If state is present, create the rule if module.params.get("state") == "present": rule_id = create_rule(module, base_url, headers, ruleset, rule) - # If specified, move rule - if move is not None: - rule_id = move_rule(module, base_url, headers, rule_id, move) + # Move rule to specified location, if it's not default + if location["position"] != "bottom": + rule_id = move_rule(module, base_url, headers, rule_id, location) exit_changed(module, "Created rule", rule_id) # If state is absent, do nothing else: From f9cc4437c9f0d6dc20de5ae5d4547dae66fb85fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Sun, 15 Jan 2023 10:50:46 +0100 Subject: [PATCH 33/65] handle state=absent --- plugins/modules/rule.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 97896a99c..0c07749d1 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -33,11 +33,13 @@ description: - Location of the rule within a folder. - By default rules are created at the bottom of the "/" folder. - - Has no effect when I(state=absent). + - Mutually exclusive with I(folder). type: dict suboptions: position: - description: Position of the rule in the folder. + description: + - Position of the rule in the folder. + - Has no effect when I(state=absent). type: str choices: - "top" @@ -49,18 +51,19 @@ description: - Put the rule C(before) or C(after) this rule_id. - Required when I(position) is C(before) or C(after). - - Mutually exclusive with I(position=top) and I(bottom). + - Mutually exclusive with I(folder). type: str folder: description: - - Put the rule at the C(top) or C(bottom) of this folder. + - Folder of the rule. - Required when I(position) is C(top) or C(bottom). - - Mutually exclusive with I(position=before) and I(after). + - Required when I(state=absent). + - Mutually exclusive with I(rule_id). default: "/" type: str folder: description: - - Folder the rule should belong to. + - Folder of the rule. - Deprecated, use I(location) instead. - Mutually exclusive with I(location). type: str @@ -159,7 +162,7 @@ rule_id: "{{ response.id }}" state: "present" -# Delete the rule described. +# Delete the first rule. - name: "Delete a rule." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -197,10 +200,10 @@ sample: 'Rule created.' id: - description: The ID of the rule, when it is created or when it already exists. + description: The ID of the rule. type: str - returned: when rule exists, is created - sample: "1f97bc43-52dc-4f1a-ab7b-c2e9553958ab" + returned: when the rule is created or when it already exists + sample: '1f97bc43-52dc-4f1a-ab7b-c2e9553958ab' """ import json @@ -449,6 +452,9 @@ def run_module(): "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") # Get ID of rule that is the same as the given options rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) From 1e71ce9a83cf09d1c3c1f0826f73f37d38305391 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Mon, 16 Jan 2023 13:10:47 +0100 Subject: [PATCH 34/65] Fix folder handling in rule module. (Issue #195) --- changelogs/fragments/bugfix_rule_module.yml | 50 +++++++++++++++++++++ plugins/modules/rule.py | 4 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/bugfix_rule_module.yml diff --git a/changelogs/fragments/bugfix_rule_module.yml b/changelogs/fragments/bugfix_rule_module.yml new file mode 100644 index 000000000..3b07abd42 --- /dev/null +++ b/changelogs/fragments/bugfix_rule_module.yml @@ -0,0 +1,50 @@ +# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to +bugfixes: + - Rule module - Now take the folder into account when checking for existing rules. + +# minor_changes: +# - Add agent role. Currently supports the vanilla agent. + +# known_issues: +# - This release is still in development and a heavy work in progress. +# - Discovery module is not feature complete yet. +# - Downtime module is not fully idempotent yet. This affects service downtimes and deletions. + +## Line Format +# When writing a changelog entry, use the following format: + +# - scope - description starting with a lowercase letter and ending with a period at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). + +# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). + +# For changes that are not really scoped (for example, which affect a whole collection), use the following format: + +# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). + + +## Possible keys: + +# breaking_changes + +# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. +# major_changes + +# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. +# minor_changes + +# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. +# deprecated_features + +# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. +# removed_features + +# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. +# security_fixes + +# Fixes that address CVEs or resolve security concerns. Include links to CVE information. +# bugfixes + +# Fixes that resolve issues. +# known_issues + +# Known issues that are currently not fixed or will not be fixed. diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 3d70dc891..86c7ed622 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -69,6 +69,7 @@ "disabled": false, "documentation_url": "https://github.com/tribe29/ansible-collection-tribe29.checkmk/blob/main/plugins/modules/rules.py" } + folder: "/" value_raw: "{'levels': (80.0, 90.0)}" state: "present" @@ -168,7 +169,8 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if ( sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) - and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) + and r["extensions"]["folder"] == rule["folder"] + and r["extensions"]["value_raw"] == rule["value_raw"] ): # If they are the same, return the ID return r["id"] From 5dd14f80b385b096fb87987dda5efb1930862211 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Mon, 16 Jan 2023 16:47:18 +0100 Subject: [PATCH 35/65] Finalize feature. Also, add use case playbook and update README. --- playbooks/usecases/remote-registration.yml | 56 ++++++++++++++++++++++ roles/agent/README.md | 7 +++ roles/agent/defaults/main.yml | 4 +- roles/agent/tasks/main.yml | 2 +- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 playbooks/usecases/remote-registration.yml diff --git a/playbooks/usecases/remote-registration.yml b/playbooks/usecases/remote-registration.yml new file mode 100644 index 000000000..870733bb7 --- /dev/null +++ b/playbooks/usecases/remote-registration.yml @@ -0,0 +1,56 @@ +--- +- name: "Register hosts against a remote site. Both for updates and TLS." + hosts: vagrant + vars: + # Basic server and authentication information. + # You have to provide the distributed setup yourself. + server_url: "http://localhost/" + checkmk_agent_version: "2.1.0p19" + checkmk_agent_edition: "cre" + checkmk_agent_user: "cmkadmin" + checkmk_agent_pass: "password" + # Here comes the part, where we get into remote registration + checkmk_agent_protocol: http + # The following should be set to the central site. + # This where you configure the host objects. + # Currently the agent package is also pulled from here. + checkmk_agent_server: localhost + checkmk_agent_site: "local" + # The following should be pointed to the respective remote site. + # This is where the registration will happen. + checkmk_agent_registration_server: "{{ checkmk_agent_server }}" + checkmk_agent_registration_site: remote + # The folder might differ from your remote site name, + # as it is the technical path. Check your configuration for this information. + checkmk_agent_folder: "/remote" + # These options need to be enabled for all registrations to work. + # You can however disable the one you do not want to perform. + # But the host needs to be added in any case. + checkmk_agent_update: 'true' + checkmk_agent_tls: 'true' + checkmk_agent_add_host: 'true' + # These are some generic agent options you might want to configure. + checkmk_agent_discover: 'true' + checkmk_agent_force_install: 'true' + checkmk_agent_delegate_api_calls: localhost + checkmk_agent_delegate_download: "{{ inventory_hostname }}" + checkmk_agent_host_name: "{{ inventory_hostname }}" + checkmk_agent_host_folder: "{{ site }}" + checkmk_agent_host_ip: "{{ ansible_host }}" + checkmk_agent_host_attributes: + ipaddress: "{{ checkmk_agent_host_ip | default(omit) }}" + roles: + - agent + post_tasks: + - name: "Activate changes." + activation: + server_url: "{{ server_url }}" + site: "{{ checkmk_agent_site }}" + automation_user: "{{ checkmk_agent_user }}" + automation_secret: "{{ checkmk_agent_pass }}" + force_foreign_changes: 'true' + sites: + - "{{ checkmk_agent_site }}" + - "{{ checkmk_agent_registration_site }}" + delegate_to: localhost + run_once: 'true' diff --git a/roles/agent/README.md b/roles/agent/README.md index 6889627b9..82fe9be75 100644 --- a/roles/agent/README.md +++ b/roles/agent/README.md @@ -157,6 +157,13 @@ passed in as parameters) is always nice for users too: roles: - tribe29.checkmk.agent +## Use Cases +This is a brief collection of use cases, that outline how this role can be used. +It should give you an idea of what is possible, but also what things to consider. + +### Agent registration against a remote site +See [remote-registration.yml](../../playbooks/usecases/remote-registration.yml). + ## Contributing See [CONTRIBUTING](../../CONTRIBUTING). diff --git a/roles/agent/defaults/main.yml b/roles/agent/defaults/main.yml index b2fd1c436..e2b482b5f 100644 --- a/roles/agent/defaults/main.yml +++ b/roles/agent/defaults/main.yml @@ -1,5 +1,5 @@ --- -checkmk_agent_version: "2.1.0p18" +checkmk_agent_version: "2.1.0p19" checkmk_agent_edition: cre checkmk_agent_protocol: http checkmk_agent_server: localhost @@ -24,7 +24,7 @@ checkmk_agent_prep_legacy: 'false' checkmk_agent_delegate_api_calls: localhost checkmk_agent_delegate_download: "{{ inventory_hostname }}" checkmk_agent_host_name: "{{ inventory_hostname }}" -checkmk_agent_folder: "/" +checkmk_agent_folder: "{{ checkmk_folder_path | default('/') }}" checkmk_agent_host_attributes: ipaddress: "{{ checkmk_agent_host_ip | default(omit) }}" diff --git a/roles/agent/tasks/main.yml b/roles/agent/tasks/main.yml index 85f94a74e..00b8cc950 100644 --- a/roles/agent/tasks/main.yml +++ b/roles/agent/tasks/main.yml @@ -109,7 +109,7 @@ become: true ansible.builtin.shell: | cmk-agent-ctl register -H {{ checkmk_agent_host_name }} \ - -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_site }} \ + -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_registration_site }} \ -U {{ checkmk_agent_user }} -P {{ checkmk_agent_auth }} --trust-cert register: checkmk_agent_tls_state when: | From 0c20c0aa081e5cee2efe26fb5e7f9b8343b5f01d Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Mon, 16 Jan 2023 17:05:40 +0100 Subject: [PATCH 36/65] Check for existence of the firewalld service. Also, use ansible.utils.ipaddr() instead of the short name, which points to a different collection and is deprecated. Fix task names. --- roles/agent/tasks/RedHat.yml | 10 +++++----- roles/agent/tasks/main.yml | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/roles/agent/tasks/RedHat.yml b/roles/agent/tasks/RedHat.yml index 387cff085..84b553b56 100644 --- a/roles/agent/tasks/RedHat.yml +++ b/roles/agent/tasks/RedHat.yml @@ -107,14 +107,14 @@ tags: - install-package -- name: "Configure Firewall for Agent." +- name: "RedHat Derivatives: Configure Firewall for Agent." block: - - name: "Check if checkmk_agent_server is an IP address." + - name: "RedHat Derivatives: Check if checkmk_agent_server is an IP address." ansible.builtin.set_fact: checkmk_agent_server_ip: "{{ checkmk_agent_server }}" - when: checkmk_agent_server_ip is not defined and checkmk_agent_server | ipaddr() + when: checkmk_agent_server_ip is not defined and checkmk_agent_server | ansible.utils.ipaddr() - - name: "Allow Checkmk services access to the agent." + - name: "RedHat Derivatives: Allow Checkmk services access to the agent." ansible.posix.firewalld: permanent: 'yes' immediate: 'yes' @@ -122,4 +122,4 @@ rich_rule: 'rule family="ipv4" source address={{ checkmk_agent_server_ip }} port port="6556" protocol="tcp" accept' when: checkmk_agent_server_ip is defined become: true - when: checkmk_agent_configure_firewall | bool + when: checkmk_agent_configure_firewall | bool and "firewalld.service" in ansible_facts.services diff --git a/roles/agent/tasks/main.yml b/roles/agent/tasks/main.yml index 00b8cc950..4264e7296 100644 --- a/roles/agent/tasks/main.yml +++ b/roles/agent/tasks/main.yml @@ -10,6 +10,9 @@ tags: - get-package-facts +- name: Populate service facts. + ansible.builtin.service_facts: + - name: "Import Legacy agent tasks." ansible.builtin.include_tasks: "legacy.yml" when: | From f9c1363d2f4e1c0bab0da832a535fa9522e23d5f Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Mon, 16 Jan 2023 17:36:23 +0100 Subject: [PATCH 37/65] Update README. --- playbooks/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/playbooks/README.md b/playbooks/README.md index e504beaa1..7f0510d53 100644 --- a/playbooks/README.md +++ b/playbooks/README.md @@ -3,11 +3,17 @@ ## Getting started To get started using these playbooks, you need to create your own [`config.yml`](./vars/config.yml) in [`./vars/`](./vars/) from the shipped [`config.yml.example`](./vars/config.yml.example). Add the details of your -Checkmk site and call a playbook. Please refer to the following table, which +Checkmk site and call a playbook. Please refer to the following tables, which playbooks are available and what they do. +## Folders +Name | Description +--- | --- +[vars](./vars/)|Contains variable files. We ship a `config.yml.example` for you to copy. +[usecases](./usecases/)|Contains playbooks for specific use cases. + ## Playbooks Name | Description --- | --- [demo.yml](./demo.yml)|Demonstrate the power of this collection against an **empty** demo site. Use this from within this repository. -[test-full.yml](./test-full.yml)|Current testing playbook. Handle with care! +[roles.yml](./roles.yml)|Run the roles contained in this collection. Use the tags `agent` and `server` to limit the run to one role. From 16ff8c05a77e809de80c1fd812210aecc2693bb3 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Tue, 17 Jan 2023 08:04:20 +0100 Subject: [PATCH 38/65] Bugfix README. --- roles/agent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/agent/README.md b/roles/agent/README.md index 82fe9be75..1d7007fc8 100644 --- a/roles/agent/README.md +++ b/roles/agent/README.md @@ -52,7 +52,7 @@ The server you want to use for registration tasks (Agent updates and TLS encrypt checkmk_agent_registration_site: "{{ checkmk_agent_site }}" -The server you want to use for registration tasks (Agent updates and TLS encryption). Defaults to {{ checkmk_agent_site }}. +The site you want to use for registration tasks (Agent updates and TLS encryption). Defaults to {{ checkmk_agent_site }}. checkmk_agent_user: automation From dee6c0973fb385c11030cf1c431502b33b2b2334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Tue, 17 Jan 2023 12:35:14 +0100 Subject: [PATCH 39/65] deprecate "folder" in favor of "location" --- plugins/modules/rule.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 0c07749d1..ea16d3b39 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -408,6 +408,13 @@ def run_module(): ], mutually_exclusive=[("folder", "rule_id")], apply_defaults=True, + deprecated_aliases=[ + dict( + name="folder", + collection_name="tribe29.checkmk", + version="1.0.0", + ), + ], ), ), mutually_exclusive=[("folder", "location")], From 7750cc8721ef54eabd38f04621bdcfc0d5bf5b94 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 17 Jan 2023 14:11:57 +0100 Subject: [PATCH 40/65] Rule module: Improve rule comparison. --- plugins/modules/rule.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 80b532f53..938188d8d 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -88,6 +88,7 @@ author: - diademiemi (@diademiemi) + - Geoffroy Stévenne (@geof77) """ EXAMPLES = r""" @@ -255,6 +256,28 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): return json.loads(response.read().decode("utf-8")) +def get_existing_rule(module, base_url, headers, ruleset, rule): + # Get rules in ruleset + rules = get_rules_in_ruleset(module, base_url, headers, ruleset) + + def _dicts_are_equal(d1, d2): + return all(d1.get(k) == d2.get(k) for k in list(d1.keys()) + list(d2.keys())) + + if rules is not None: + # Loop through all rules + for r in rules.get("value"): + if ( + _dicts_are_equal(r["extensions"]["conditions"], rule["conditions"]) + and _dicts_are_equal(r["extensions"]["properties"], rule["properties"]) + and r["extensions"]["folder"] == rule["folder"] + and r["extensions"]["value_raw"] == rule["value_raw"] + + ): + # If they are the same, return the ID + return r["id"] + return None + + def get_existing_rule(module, base_url, headers, ruleset, rule): # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) From 47d105aa23a4f8e6512d9aa914a0af67744525a1 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 17 Jan 2023 14:12:21 +0100 Subject: [PATCH 41/65] Added some changelogs. --- changelogs/fragments/bugfix_rule_module_2.yml | 50 +++++++++++++++++++ changelogs/fragments/rule_module.yml | 50 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 changelogs/fragments/bugfix_rule_module_2.yml create mode 100644 changelogs/fragments/rule_module.yml diff --git a/changelogs/fragments/bugfix_rule_module_2.yml b/changelogs/fragments/bugfix_rule_module_2.yml new file mode 100644 index 000000000..e56d67cb7 --- /dev/null +++ b/changelogs/fragments/bugfix_rule_module_2.yml @@ -0,0 +1,50 @@ +# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to +bugfixes: + - Rule module - Compare the keys AND values of dicts when checking for existing rules. + +# minor_changes: +# - Add agent role. Currently supports the vanilla agent. + +# known_issues: +# - This release is still in development and a heavy work in progress. +# - Discovery module is not feature complete yet. +# - Downtime module is not fully idempotent yet. This affects service downtimes and deletions. + +## Line Format +# When writing a changelog entry, use the following format: + +# - scope - description starting with a lowercase letter and ending with a period at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). + +# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). + +# For changes that are not really scoped (for example, which affect a whole collection), use the following format: + +# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). + + +## Possible keys: + +# breaking_changes + +# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. +# major_changes + +# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. +# minor_changes + +# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. +# deprecated_features + +# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. +# removed_features + +# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. +# security_fixes + +# Fixes that address CVEs or resolve security concerns. Include links to CVE information. +# bugfixes + +# Fixes that resolve issues. +# known_issues + +# Known issues that are currently not fixed or will not be fixed. diff --git a/changelogs/fragments/rule_module.yml b/changelogs/fragments/rule_module.yml new file mode 100644 index 000000000..bd1454553 --- /dev/null +++ b/changelogs/fragments/rule_module.yml @@ -0,0 +1,50 @@ +# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to +#bugfixes: +# - Rule module - Now take the folder into account when checking for existing rules. + +minor_changes: + - Rule module - Now its possible to choose a position when creating a rule. The ID of the created rule is returned in the task's response. + +# known_issues: +# - This release is still in development and a heavy work in progress. +# - Discovery module is not feature complete yet. +# - Downtime module is not fully idempotent yet. This affects service downtimes and deletions. + +## Line Format +# When writing a changelog entry, use the following format: + +# - scope - description starting with a lowercase letter and ending with a period at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). + +# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). + +# For changes that are not really scoped (for example, which affect a whole collection), use the following format: + +# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). + + +## Possible keys: + +# breaking_changes + +# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. +# major_changes + +# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. +# minor_changes + +# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. +# deprecated_features + +# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. +# removed_features + +# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. +# security_fixes + +# Fixes that address CVEs or resolve security concerns. Include links to CVE information. +# bugfixes + +# Fixes that resolve issues. +# known_issues + +# Known issues that are currently not fixed or will not be fixed. From 6626e5e2b865c7fbd3414196095bcda95374e67f Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Tue, 17 Jan 2023 14:36:12 +0100 Subject: [PATCH 42/65] Make black happy. --- plugins/modules/rule.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 938188d8d..6411a4dfd 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -243,15 +243,12 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): url = "%s%s?%s" % (base_url, api_endpoint, urlencode(params)) - response, info = fetch_url( - module, url, module.jsonify(params), headers=headers, method="GET" - ) + response, info = fetch_url(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"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) return json.loads(response.read().decode("utf-8")) @@ -271,7 +268,6 @@ def _dicts_are_equal(d1, d2): and _dicts_are_equal(r["extensions"]["properties"], rule["properties"]) and r["extensions"]["folder"] == rule["folder"] and r["extensions"]["value_raw"] == rule["value_raw"] - ): # If they are the same, return the ID return r["id"] @@ -309,15 +305,12 @@ def create_rule(module, base_url, headers, ruleset, rule): url = base_url + api_endpoint - response, info = fetch_url( - module, url, module.jsonify(params), headers=headers, method="POST" - ) + response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="POST") if info["status"] != 200: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) r = json.loads(response.read().decode("utf-8")) @@ -335,8 +328,7 @@ def get_rule_etag(module, base_url, headers, rule_id): if info["status"] not in [200, 204]: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) return info["etag"] @@ -363,15 +355,12 @@ def move_rule(module, base_url, headers, rule_id, location): url = base_url + api_endpoint - response, info = fetch_url( - module, url, module.jsonify(params), headers=headers, method="POST" - ) + response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="POST") if info["status"] not in [200, 204]: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) r = json.loads(response.read().decode("utf-8")) @@ -389,8 +378,7 @@ def delete_rule(module, base_url, headers, rule_id): if info["status"] != 204: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) From 9d3b4da60d4a639e4ed8ea9972aaaf8b0c0c6867 Mon Sep 17 00:00:00 2001 From: Nicolas Vongeheur Date: Tue, 17 Jan 2023 17:53:58 +0100 Subject: [PATCH 43/65] Rename SLES VM --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 314f05dd9..36429f896 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -104,7 +104,7 @@ end srv.vm.network "private_network", ip: "192.168.56.65" srv.ssh.insert_key = false srv.vm.provider "virtualbox" do |v| - v.name = 'ansuse' + v.name = 'ansles' v.memory = 2048 v.cpus = 2 end From f2042261dfafd9fef5f05551bca6353654e30136 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Thu, 19 Jan 2023 09:14:02 +0100 Subject: [PATCH 44/65] Update use case playbook. --- playbooks/usecases/remote-registration.yml | 29 ++++++++-------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/playbooks/usecases/remote-registration.yml b/playbooks/usecases/remote-registration.yml index 870733bb7..0922e059e 100644 --- a/playbooks/usecases/remote-registration.yml +++ b/playbooks/usecases/remote-registration.yml @@ -1,10 +1,13 @@ --- +# This playbook uses the inventory from the 'playbooks/hosts' file and expects +# an expects an existing site with the below configuration and hosts of the group +# 'vagrant'. + - name: "Register hosts against a remote site. Both for updates and TLS." hosts: vagrant vars: # Basic server and authentication information. # You have to provide the distributed setup yourself. - server_url: "http://localhost/" checkmk_agent_version: "2.1.0p19" checkmk_agent_edition: "cre" checkmk_agent_user: "cmkadmin" @@ -14,18 +17,19 @@ # The following should be set to the central site. # This where you configure the host objects. # Currently the agent package is also pulled from here. - checkmk_agent_server: localhost - checkmk_agent_site: "local" + checkmk_agent_server: 192.168.56.1 + checkmk_agent_site: "ansible" # The following should be pointed to the respective remote site. # This is where the registration will happen. checkmk_agent_registration_server: "{{ checkmk_agent_server }}" - checkmk_agent_registration_site: remote + checkmk_agent_registration_site: ansible_remote_1 # The folder might differ from your remote site name, # as it is the technical path. Check your configuration for this information. - checkmk_agent_folder: "/remote" + checkmk_agent_folder: "/remote_1" # These options need to be enabled for all registrations to work. # You can however disable the one you do not want to perform. - # But the host needs to be added in any case. + # But the host needs to be added and changes activated in any case. + checkmk_agent_auto_activate: 'true' checkmk_agent_update: 'true' checkmk_agent_tls: 'true' checkmk_agent_add_host: 'true' @@ -41,16 +45,3 @@ ipaddress: "{{ checkmk_agent_host_ip | default(omit) }}" roles: - agent - post_tasks: - - name: "Activate changes." - activation: - server_url: "{{ server_url }}" - site: "{{ checkmk_agent_site }}" - automation_user: "{{ checkmk_agent_user }}" - automation_secret: "{{ checkmk_agent_pass }}" - force_foreign_changes: 'true' - sites: - - "{{ checkmk_agent_site }}" - - "{{ checkmk_agent_registration_site }}" - delegate_to: localhost - run_once: 'true' From 7774828493d4e2e5745a59c0ea8b85cac3f5c170 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Thu, 19 Jan 2023 09:15:13 +0100 Subject: [PATCH 45/65] Enable automatic activate changes. --- roles/agent/README.md | 5 +++++ roles/agent/defaults/main.yml | 1 + roles/agent/handlers/main.yml | 12 ++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 roles/agent/handlers/main.yml diff --git a/roles/agent/README.md b/roles/agent/README.md index 1d7007fc8..fd471f8c7 100644 --- a/roles/agent/README.md +++ b/roles/agent/README.md @@ -68,6 +68,11 @@ This is mutually exclusive with `checkmk_agent_secret`. The secret for the automation user used to authenticate against your Checkmk site. This is mutually exclusive with `checkmk_agent_pass`. + checkmk_agent_auto_activate: 'false' + +Enable automatic activation of changes on all sites. +This is disabled by default, as it might be unexpected. + checkmk_agent_add_host: 'false' Automatically add the host where the agent was installed to Checkmk. diff --git a/roles/agent/defaults/main.yml b/roles/agent/defaults/main.yml index e2b482b5f..4ee53a1f6 100644 --- a/roles/agent/defaults/main.yml +++ b/roles/agent/defaults/main.yml @@ -14,6 +14,7 @@ checkmk_agent_user: "{{ automation_user | default('automation') }}" # checkmk_agent_pass: "{{ automation_secret }}" # checkmk_agent_secret: "{{ automation_secret }}" +checkmk_agent_auto_activate: 'false' checkmk_agent_add_host: 'false' checkmk_agent_discover: 'false' checkmk_agent_update: 'false' diff --git a/roles/agent/handlers/main.yml b/roles/agent/handlers/main.yml new file mode 100644 index 000000000..b9360a55f --- /dev/null +++ b/roles/agent/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: "Activate Changes." + listen: activate changes + tribe29.checkmk.activation: + server_url: "{{ checkmk_agent_protocol }}://{{ checkmk_agent_server }}" + site: "{{ checkmk_agent_site }}" + automation_user: "{{ checkmk_agent_user }}" + automation_secret: "{{ checkmk_agent_pass }}" + force_foreign_changes: 'false' + delegate_to: localhost + run_once: 'true' + when: checkmk_agent_auto_activate | bool From 268820c1666ee9319cc8422be77f11c47f9f475e Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Thu, 19 Jan 2023 09:16:35 +0100 Subject: [PATCH 46/65] Fix host creation on remote site. Also enable automatic activate changes. --- roles/agent/tasks/main.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/roles/agent/tasks/main.yml b/roles/agent/tasks/main.yml index 4264e7296..8d0480991 100644 --- a/roles/agent/tasks/main.yml +++ b/roles/agent/tasks/main.yml @@ -50,7 +50,7 @@ - name: "Create host on server." tribe29.checkmk.host: - server_url: "{{ checkmk_agent_protocol }}://{{ checkmk_agent_registration_server }}:{{ checkmk_agent_port }}/" + server_url: "{{ checkmk_agent_protocol }}://{{ checkmk_agent_server }}:{{ checkmk_agent_port }}/" site: "{{ checkmk_agent_site }}" validate_certs: "{{ checkmk_agent_server_validate_certs | bool }}" automation_user: "{{ checkmk_agent_user }}" @@ -66,6 +66,7 @@ ("The host is already part of the specified target folder" not in checkmk_agent_create_result.msg) delegate_to: "{{ checkmk_agent_delegate_api_calls }}" when: checkmk_agent_add_host | bool + notify: "activate changes" - name: "Check for Agent Updater Binary." ansible.builtin.stat: @@ -81,7 +82,7 @@ become: true ansible.builtin.shell: | cmk-update-agent register -H {{ checkmk_agent_host_name }} \ - -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_site }} -p {{ checkmk_agent_protocol }} \ + -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_registration_site }} -p {{ checkmk_agent_protocol }} \ -U {{ checkmk_agent_user }} -P {{ checkmk_agent_auth }} register: checkmk_agent_update_state when: | @@ -97,7 +98,7 @@ become: true ansible.builtin.shell: | cmk-update-agent register -H {{ checkmk_agent_host_name }} \ - -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_site }} -p {{ checkmk_agent_protocol }} \ + -s {{ checkmk_agent_registration_server }} -i {{ checkmk_agent_registration_site }} -p {{ checkmk_agent_protocol }} \ -U {{ checkmk_agent_user }} -S {{ checkmk_agent_auth }} register: checkmk_agent_update_state when: | @@ -108,6 +109,10 @@ and checkmk_agent_update | bool and (checkmk_agent_secret is defined and checkmk_agent_secret | length) +- name: "Trigger Activate Changes to enable TLS registration." + ansible.builtin.meta: + flush_handlers + - name: "Register Agent for TLS." become: true ansible.builtin.shell: | @@ -116,10 +121,7 @@ -U {{ checkmk_agent_user }} -P {{ checkmk_agent_auth }} --trust-cert register: checkmk_agent_tls_state when: | - (checkmk_agent_edition == "cee" or - checkmk_agent_edition == "cfe" or - checkmk_agent_edition == "cme") - and checkmk_agent_controller_binary.stat.exists | bool + checkmk_agent_controller_binary.stat.exists | bool and checkmk_agent_tls | bool and (checkmk_agent_auth is defined and checkmk_agent_auth | length) @@ -134,3 +136,4 @@ state: "fix_all" delegate_to: "{{ checkmk_agent_delegate_api_calls }}" when: checkmk_agent_discover | bool + notify: "activate changes" From 8634c5ad08a08ada670897c85fbc1096ad265ad1 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Thu, 19 Jan 2023 11:22:36 +0100 Subject: [PATCH 47/65] Rule module: temporarily don't compare value_raw when checking for existing rules. --- changelogs/fragments/bugfix_rule_module_2.yml | 3 +- plugins/modules/rule.py | 55 ++++++++----------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/changelogs/fragments/bugfix_rule_module_2.yml b/changelogs/fragments/bugfix_rule_module_2.yml index e56d67cb7..3f867f181 100644 --- a/changelogs/fragments/bugfix_rule_module_2.yml +++ b/changelogs/fragments/bugfix_rule_module_2.yml @@ -1,6 +1,7 @@ # https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to bugfixes: - - Rule module - Compare the keys AND values of dicts when checking for existing rules. + - Rule module - Compare the complete dicts, and not only their keys when checking for existing rules. + - Rule module - Currently, value_raw is not being checked when comparing rules. # minor_changes: # - Add agent role. Currently supports the vanilla agent. diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 6411a4dfd..4b581c97f 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -20,6 +20,7 @@ description: - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. +- Currently, the idempotency of this module is restricted: To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. See https://github.com/tribe29/ansible-collection-tribe29.checkmk/issues/186#issuecomment-1396692373 extends_documentation_fragment: [tribe29.checkmk.common] @@ -243,12 +244,15 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): url = "%s%s?%s" % (base_url, api_endpoint, urlencode(params)) - response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="GET") + response, info = fetch_url( + 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"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) return json.loads(response.read().decode("utf-8")) @@ -257,35 +261,14 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) - def _dicts_are_equal(d1, d2): - return all(d1.get(k) == d2.get(k) for k in list(d1.keys()) + list(d2.keys())) - - if rules is not None: - # Loop through all rules - for r in rules.get("value"): - if ( - _dicts_are_equal(r["extensions"]["conditions"], rule["conditions"]) - and _dicts_are_equal(r["extensions"]["properties"], rule["properties"]) - and r["extensions"]["folder"] == rule["folder"] - and r["extensions"]["value_raw"] == rule["value_raw"] - ): - # If they are the same, return the ID - return r["id"] - return None - - -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 rules is not None: # Loop through all rules for r in rules.get("value"): - # Check if conditions, properties and values are the same if ( - sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) - and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) + r["extensions"]["conditions"] == rule["conditions"] + and r["extensions"]["properties"] == rule["properties"] and r["extensions"]["folder"] == rule["folder"] - and r["extensions"]["value_raw"] == rule["value_raw"] + # and r["extensions"]["value_raw"] == rule["value_raw"] ): # If they are the same, return the ID return r["id"] @@ -305,12 +288,15 @@ def create_rule(module, base_url, headers, ruleset, rule): url = base_url + api_endpoint - response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="POST") + response, info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="POST" + ) if info["status"] != 200: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) r = json.loads(response.read().decode("utf-8")) @@ -328,7 +314,8 @@ def get_rule_etag(module, base_url, headers, rule_id): if info["status"] not in [200, 204]: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) return info["etag"] @@ -355,12 +342,15 @@ def move_rule(module, base_url, headers, rule_id, location): url = base_url + api_endpoint - response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="POST") + response, info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="POST" + ) if info["status"] not in [200, 204]: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) r = json.loads(response.read().decode("utf-8")) @@ -378,7 +368,8 @@ def delete_rule(module, base_url, headers, rule_id): if info["status"] != 204: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) From 6662d1ccf979f65a0ebcefd0434df67c4ae6cc3b Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Thu, 19 Jan 2023 11:22:36 +0100 Subject: [PATCH 48/65] Rule module: temporarily don't compare value_raw when checking for existing rules. --- changelogs/fragments/bugfix_rule_module_2.yml | 3 +- plugins/modules/rule.py | 55 ++++++++----------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/changelogs/fragments/bugfix_rule_module_2.yml b/changelogs/fragments/bugfix_rule_module_2.yml index e56d67cb7..3f867f181 100644 --- a/changelogs/fragments/bugfix_rule_module_2.yml +++ b/changelogs/fragments/bugfix_rule_module_2.yml @@ -1,6 +1,7 @@ # https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to bugfixes: - - Rule module - Compare the keys AND values of dicts when checking for existing rules. + - Rule module - Compare the complete dicts, and not only their keys when checking for existing rules. + - Rule module - Currently, value_raw is not being checked when comparing rules. # minor_changes: # - Add agent role. Currently supports the vanilla agent. diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 6411a4dfd..15079ee57 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -20,6 +20,7 @@ description: - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. +- Currently, the idempotency of this module is restricted: To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. extends_documentation_fragment: [tribe29.checkmk.common] @@ -243,12 +244,15 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): url = "%s%s?%s" % (base_url, api_endpoint, urlencode(params)) - response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="GET") + response, info = fetch_url( + 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"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) return json.loads(response.read().decode("utf-8")) @@ -257,35 +261,14 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): # Get rules in ruleset rules = get_rules_in_ruleset(module, base_url, headers, ruleset) - def _dicts_are_equal(d1, d2): - return all(d1.get(k) == d2.get(k) for k in list(d1.keys()) + list(d2.keys())) - - if rules is not None: - # Loop through all rules - for r in rules.get("value"): - if ( - _dicts_are_equal(r["extensions"]["conditions"], rule["conditions"]) - and _dicts_are_equal(r["extensions"]["properties"], rule["properties"]) - and r["extensions"]["folder"] == rule["folder"] - and r["extensions"]["value_raw"] == rule["value_raw"] - ): - # If they are the same, return the ID - return r["id"] - return None - - -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 rules is not None: # Loop through all rules for r in rules.get("value"): - # Check if conditions, properties and values are the same if ( - sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) - and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) + r["extensions"]["conditions"] == rule["conditions"] + and r["extensions"]["properties"] == rule["properties"] and r["extensions"]["folder"] == rule["folder"] - and r["extensions"]["value_raw"] == rule["value_raw"] + # and r["extensions"]["value_raw"] == rule["value_raw"] ): # If they are the same, return the ID return r["id"] @@ -305,12 +288,15 @@ def create_rule(module, base_url, headers, ruleset, rule): url = base_url + api_endpoint - response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="POST") + response, info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="POST" + ) if info["status"] != 200: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) r = json.loads(response.read().decode("utf-8")) @@ -328,7 +314,8 @@ def get_rule_etag(module, base_url, headers, rule_id): if info["status"] not in [200, 204]: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) return info["etag"] @@ -355,12 +342,15 @@ def move_rule(module, base_url, headers, rule_id, location): url = base_url + api_endpoint - response, info = fetch_url(module, url, module.jsonify(params), headers=headers, method="POST") + response, info = fetch_url( + module, url, module.jsonify(params), headers=headers, method="POST" + ) if info["status"] not in [200, 204]: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) r = json.loads(response.read().decode("utf-8")) @@ -378,7 +368,8 @@ def delete_rule(module, base_url, headers, rule_id): if info["status"] != 204: exit_failed( module, - "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), ) From 1e9a2e999d5bd77afd3a1f1331dd6ea88e8a838b Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Thu, 19 Jan 2023 11:42:35 +0100 Subject: [PATCH 49/65] Sanity Checks... --- plugins/modules/rule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 15079ee57..2fd931056 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -20,7 +20,8 @@ description: - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. -- Currently, the idempotency of this module is restricted: To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. +- Currently, the idempotency of this module is restricted: + To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. extends_documentation_fragment: [tribe29.checkmk.common] From c6a94e699fde1eda0503d09288688a6b3a98b5eb Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Thu, 19 Jan 2023 12:15:02 +0100 Subject: [PATCH 50/65] Corrected the integration tests for the rule module. --- plugins/modules/rule.py | 8 ++++---- tests/integration/targets/rule/vars/main.yml | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 2fd931056..7d2d68f3f 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -18,10 +18,10 @@ version_added: "0.10.0" description: -- Manage rules within Checkmk. Importing rules from the output of the Checkmk API. -- Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. -- Currently, the idempotency of this module is restricted: - To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. + - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. + - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. + - Currently, the idempotency of this module is restricted. + - To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. extends_documentation_fragment: [tribe29.checkmk.common] diff --git a/tests/integration/targets/rule/vars/main.yml b/tests/integration/targets/rule/vars/main.yml index dd3403583..ab3399a46 100644 --- a/tests/integration/targets/rule/vars/main.yml +++ b/tests/integration/targets/rule/vars/main.yml @@ -31,10 +31,7 @@ checkmk_rules: rule: conditions: { "host_labels": [], - "host_name": { - "match_on": [], - "operator": "one_of" - }, + "host_name": {}, "host_tags": [], "service_labels": [] } From af19fe19c1aeb050f864824dc502550cff041801 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Thu, 19 Jan 2023 12:15:02 +0100 Subject: [PATCH 51/65] Corrected the integration tests for the rule module. --- plugins/modules/rule.py | 8 ++++---- tests/integration/targets/rule/vars/main.yml | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 2fd931056..7d2d68f3f 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -18,10 +18,10 @@ version_added: "0.10.0" description: -- Manage rules within Checkmk. Importing rules from the output of the Checkmk API. -- Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. -- Currently, the idempotency of this module is restricted: - To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. + - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. + - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. + - Currently, the idempotency of this module is restricted. + - To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. extends_documentation_fragment: [tribe29.checkmk.common] diff --git a/tests/integration/targets/rule/vars/main.yml b/tests/integration/targets/rule/vars/main.yml index dd3403583..ab3399a46 100644 --- a/tests/integration/targets/rule/vars/main.yml +++ b/tests/integration/targets/rule/vars/main.yml @@ -31,10 +31,7 @@ checkmk_rules: rule: conditions: { "host_labels": [], - "host_name": { - "match_on": [], - "operator": "one_of" - }, + "host_name": {}, "host_tags": [], "service_labels": [] } From 03f430ad22dcf0b68f15a62c7ff4511e32d09681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 17:51:46 +0100 Subject: [PATCH 52/65] get the api representation of the new rule and compare to existing --- plugins/modules/rule.py | 126 ++++++++++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 42 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 80b532f53..5d70672c2 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -261,19 +261,18 @@ 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"): - # Check if conditions, properties and values are the same if ( - sorted(r["extensions"]["conditions"]) == sorted(rule["conditions"]) - and sorted(r["extensions"]["properties"]) == sorted(rule["properties"]) - and r["extensions"]["folder"] == rule["folder"] - and r["extensions"]["value_raw"] == rule["value_raw"] + r["extensions"]["conditions"] == rule["extensions"]["conditions"] + and r["extensions"]["properties"] == rule["extensions"]["properties"] + and r["extensions"]["folder"] == rule["extensions"]["folder"] + and r["extensions"]["value_raw"] == rule["extensions"]["value_raw"] ): # If they are the same, return the ID - return r["id"] + return r return None -def create_rule(module, base_url, headers, ruleset, rule): +def get_api_repr(module, base_url, headers, ruleset, rule): api_endpoint = "/domain-types/rule/collections/all" params = { @@ -299,7 +298,74 @@ def create_rule(module, base_url, headers, ruleset, rule): r = json.loads(response.read().decode("utf-8")) - return r["id"] + return r + + +def create_rule(module, base_url, headers, ruleset, rule): + + existed = False + created = True + + # get API representation of the rule + r = get_api_repr(module, base_url, headers, ruleset, rule) + + #rsp = {"r": r, "changed": False, "failed": True} + #module.exit_json(**rsp) + + # compare the API output to existing rules + e = get_existing_rule(module, base_url, headers, ruleset, r) + + #rsp = {"e": e, "r": r, "changed": False, "failed": True} + #module.exit_json(**rsp) + + # if existing rule, delete new rule and return existing id + if e: + delete_rule(module, base_url, headers, r["id"]) + return (e["id"], existed) + + # else return new rule id + return (r["id"], created) + + +def delete_rule(module, base_url, headers, ruleset, rule): + + existed = True + + # get API representation of the rule + r = get_api_repr(module, base_url, headers, ruleset, rule) + + #rsp = {"r": r, "changed": False, "failed": True} + #module.exit_json(**rsp) + + # compare the API output to existing rules + e = get_existing_rule(module, base_url, headers, ruleset, r) + + #rsp = {"e": e, "r": r, "changed": False, "failed": True} + #module.exit_json(**rsp) + + # if existing rule, delete both + if e: + delete_rule_by_id(module, base_url, headers, r["id"]) + delete_rule_by_id(module, base_url, headers, e["id"]) + return existed + else: + delete_rule_by_id(module, base_url, headers, e["id"]) + return not existed + + +def delete_rule_by_id(module, base_url, headers, rule_id): + api_endpoint = "/objects/rule/" + + url = "%s%s%s" % (base_url, api_endpoint, rule_id) + + response, info = fetch_url(module, url, headers=headers, method="DELETE") + + if info["status"] != 204: + exit_failed( + module, + "Error calling API. HTTP code %d. Details: %s, " + % (info["status"], info["body"]), + ) def get_rule_etag(module, base_url, headers, rule_id): @@ -356,21 +422,6 @@ def move_rule(module, base_url, headers, rule_id, location): return r["id"] -def delete_rule(module, base_url, headers, rule_id): - api_endpoint = "/objects/rule/" - - url = "%s%s%s" % (base_url, api_endpoint, rule_id) - - response, info = fetch_url(module, url, headers=headers, method="DELETE") - - if info["status"] != 204: - exit_failed( - module, - "Error calling API. HTTP code %d. Details: %s, " - % (info["status"], info["body"]), - ) - - def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( @@ -465,30 +516,21 @@ def run_module(): if location.get("rule_id") is not None: exit_failed(module, "rule_id in location is invalid with state=absent") - # Get ID of rule that is the same as the given options - rule_id = get_existing_rule(module, base_url, headers, ruleset, rule) - - # If rule exists - if rule_id is not None: - # If state is absent, delete the rule - if module.params.get("state") == "absent": - delete_rule(module, base_url, headers, rule_id) + # If state is absent, delete the rule + if module.params.get("state") == "absent": + deleted = delete_rule(module, base_url, headers, ruleset, rule) + if deleted: exit_changed(module, "Deleted rule") - # If state is present, do nothing else: - exit_ok(module, "Rule already exists", rule_id) - # If rule does not exist - else: - # If state is present, create the rule - if module.params.get("state") == "present": - rule_id = create_rule(module, base_url, headers, ruleset, rule) + exit_ok(module, "Rule did not exist") + elif module.params.get("state") == "present": + (rule_id, created) = create_rule(module, base_url, headers, ruleset, rule) + if created: # Move rule to specified location, if it's not default if location["position"] != "bottom": - rule_id = move_rule(module, base_url, headers, rule_id, location) + move_rule(module, base_url, headers, rule_id, location) exit_changed(module, "Created rule", rule_id) - # If state is absent, do nothing - else: - exit_ok(module, "Rule did not exist") + exit_ok("Rule already exists") # Fallback exit_failed(module, "Unknown error") From 7eeab426685e19ece994c45ea5deba0525d5459f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 18:09:32 +0100 Subject: [PATCH 53/65] fixes --- plugins/modules/rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 5d70672c2..85d0df26c 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -320,7 +320,7 @@ def create_rule(module, base_url, headers, ruleset, rule): # if existing rule, delete new rule and return existing id if e: - delete_rule(module, base_url, headers, r["id"]) + delete_rule_by_id(module, base_url, headers, r["id"]) return (e["id"], existed) # else return new rule id @@ -530,7 +530,7 @@ def run_module(): if location["position"] != "bottom": move_rule(module, base_url, headers, rule_id, location) exit_changed(module, "Created rule", rule_id) - exit_ok("Rule already exists") + exit_ok(module,"Rule already exists") # Fallback exit_failed(module, "Unknown error") From 65b8c908a549db0841b0ace324138d9c88b5df1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 18:18:33 +0100 Subject: [PATCH 54/65] fix not deleting the repr rule --- plugins/modules/rule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 85d0df26c..5ceed273d 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -262,7 +262,8 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): # Loop through all rules for r in rules.get("value"): if ( - r["extensions"]["conditions"] == rule["extensions"]["conditions"] + r["id"] != rule["id"] + and r["extensions"]["conditions"] == rule["extensions"]["conditions"] and r["extensions"]["properties"] == rule["extensions"]["properties"] and r["extensions"]["folder"] == rule["extensions"]["folder"] and r["extensions"]["value_raw"] == rule["extensions"]["value_raw"] From 505f259ad573e94bdef990dbfcc0859c7eb89e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 21:02:57 +0100 Subject: [PATCH 55/65] cleanup the code --- plugins/modules/rule.py | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 5ceed273d..155f01bc6 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -252,6 +252,7 @@ def get_rules_in_ruleset(module, base_url, headers, ruleset): "Error calling API. HTTP code %d. Details: %s, " % (info["status"], info["body"]), ) + return json.loads(response.read().decode("utf-8")) @@ -270,6 +271,7 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): ): # If they are the same, return the ID return r + return None @@ -304,25 +306,18 @@ def get_api_repr(module, base_url, headers, ruleset, rule): def create_rule(module, base_url, headers, ruleset, rule): - existed = False created = True # get API representation of the rule r = get_api_repr(module, base_url, headers, ruleset, rule) - #rsp = {"r": r, "changed": False, "failed": True} - #module.exit_json(**rsp) - # compare the API output to existing rules e = get_existing_rule(module, base_url, headers, ruleset, r) - #rsp = {"e": e, "r": r, "changed": False, "failed": True} - #module.exit_json(**rsp) - - # if existing rule, delete new rule and return existing id + # if existing rule found, delete new rule and return existing id if e: delete_rule_by_id(module, base_url, headers, r["id"]) - return (e["id"], existed) + return (e["id"], not created) # else return new rule id return (r["id"], created) @@ -330,28 +325,22 @@ def create_rule(module, base_url, headers, ruleset, rule): def delete_rule(module, base_url, headers, ruleset, rule): - existed = True + deleted = True # get API representation of the rule r = get_api_repr(module, base_url, headers, ruleset, rule) - #rsp = {"r": r, "changed": False, "failed": True} - #module.exit_json(**rsp) - # compare the API output to existing rules e = get_existing_rule(module, base_url, headers, ruleset, r) - #rsp = {"e": e, "r": r, "changed": False, "failed": True} - #module.exit_json(**rsp) - - # if existing rule, delete both + # if existing rule found, delete both if e: delete_rule_by_id(module, base_url, headers, r["id"]) delete_rule_by_id(module, base_url, headers, e["id"]) - return existed + return deleted else: delete_rule_by_id(module, base_url, headers, e["id"]) - return not existed + return not deleted def delete_rule_by_id(module, base_url, headers, rule_id): @@ -420,8 +409,6 @@ def move_rule(module, base_url, headers, rule_id, location): r = json.loads(response.read().decode("utf-8")) - return r["id"] - def run_module(): # define available arguments/parameters a user can pass to the module @@ -521,16 +508,17 @@ def run_module(): if module.params.get("state") == "absent": deleted = delete_rule(module, base_url, headers, ruleset, rule) if deleted: - exit_changed(module, "Deleted rule") + exit_changed(module, "Rule deleted") else: exit_ok(module, "Rule did 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: # Move rule to specified location, if it's not default if location["position"] != "bottom": move_rule(module, base_url, headers, rule_id, location) - exit_changed(module, "Created rule", rule_id) + exit_changed(module, "Rule created", rule_id) exit_ok(module,"Rule already exists") # Fallback From 246afd19504f41f3e839070a227bb1e995aa0040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 21:10:03 +0100 Subject: [PATCH 56/65] ignore comments and description in comparison --- plugins/modules/rule.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 155f01bc6..9481d994b 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -265,7 +265,8 @@ def get_existing_rule(module, base_url, headers, ruleset, rule): if ( r["id"] != rule["id"] and r["extensions"]["conditions"] == rule["extensions"]["conditions"] - and r["extensions"]["properties"] == rule["extensions"]["properties"] + and r["extensions"]["properties"]["disabled"] + == rule["extensions"]["properties"]["disabled"] and r["extensions"]["folder"] == rule["extensions"]["folder"] and r["extensions"]["value_raw"] == rule["extensions"]["value_raw"] ): @@ -519,7 +520,7 @@ def run_module(): if location["position"] != "bottom": move_rule(module, base_url, headers, rule_id, location) exit_changed(module, "Rule created", rule_id) - exit_ok(module,"Rule already exists") + exit_ok(module, "Rule already exists") # Fallback exit_failed(module, "Unknown error") From 69a069d4dd13128b08c1542a66acd7ef07a9657a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 21:24:05 +0100 Subject: [PATCH 57/65] fix delete_rule --- plugins/modules/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 9481d994b..9ad4a4504 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -340,7 +340,7 @@ def delete_rule(module, base_url, headers, ruleset, rule): delete_rule_by_id(module, base_url, headers, e["id"]) return deleted else: - delete_rule_by_id(module, base_url, headers, e["id"]) + delete_rule_by_id(module, base_url, headers, r["id"]) return not deleted From 509fb9d7326f64b636070c00b06c5c729ceeea79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 22:00:02 +0100 Subject: [PATCH 58/65] return id when already exists --- plugins/modules/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 9ad4a4504..e806847e4 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -520,7 +520,7 @@ def run_module(): if location["position"] != "bottom": move_rule(module, base_url, headers, rule_id, location) exit_changed(module, "Rule created", rule_id) - exit_ok(module, "Rule already exists") + exit_ok(module, "Rule already exists", rule_id) # Fallback exit_failed(module, "Unknown error") From 11b1f481eea4e0fa4ee791e788cfb96597c2c88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffroy=20St=C3=A9venne?= Date: Thu, 19 Jan 2023 23:01:20 +0100 Subject: [PATCH 59/65] did not exist -> does not exist --- plugins/modules/rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index e806847e4..452ec5aaf 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -511,7 +511,7 @@ def run_module(): if deleted: exit_changed(module, "Rule deleted") else: - exit_ok(module, "Rule did not exist") + 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) From 394cc367e7fe00cb3b677827b502f3b1c2db2367 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Fri, 20 Jan 2023 09:57:22 +0100 Subject: [PATCH 60/65] Disable automatic molecule tests. See #196. --- .github/workflows/molecule-role-server.yaml | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/molecule-role-server.yaml b/.github/workflows/molecule-role-server.yaml index 1546acb3f..a2b5217d3 100644 --- a/.github/workflows/molecule-role-server.yaml +++ b/.github/workflows/molecule-role-server.yaml @@ -8,18 +8,18 @@ name: Molecule Tests for Server Role on: workflow_dispatch: - pull_request: - branches: - - main - - devel - paths: - - 'roles/server/**' - push: - branches: - - main - - devel - paths: - - 'roles/server/**' + # pull_request: + # branches: + # - main + # - devel + # paths: + # - 'roles/server/**' + # push: + # branches: + # - main + # - devel + # paths: + # - 'roles/server/**' env: NAMESPACE: tribe29 From a7183a12aa9a875dbf830888299ff76bb67f643d Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Fri, 20 Jan 2023 09:57:35 +0100 Subject: [PATCH 61/65] Fix python requirements. --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3f185d458..bb932675d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -ansible >= 7.0.0 +ansible >= 6.7.0 antsibull-changelog >= 0.16.0 antsibull-docs >= 1.5.0 ansible-lint >= 6.0.0 jinja2 >= 3.0.0 molecule >= 3.5.0 -molecule-docker >= 1.1.0 \ No newline at end of file +molecule-docker >= 1.1.0 From be74b3032ccbe0d2f659f5908a7cbc30b8f5e332 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Fri, 20 Jan 2023 10:05:35 +0100 Subject: [PATCH 62/65] Add changelogs. --- changelogs/fragments/agent.yml | 45 ++++++++++++++++++++++++++++++ changelogs/fragments/playbooks.yml | 3 ++ 2 files changed, 48 insertions(+) create mode 100644 changelogs/fragments/agent.yml create mode 100644 changelogs/fragments/playbooks.yml diff --git a/changelogs/fragments/agent.yml b/changelogs/fragments/agent.yml new file mode 100644 index 000000000..e936371a1 --- /dev/null +++ b/changelogs/fragments/agent.yml @@ -0,0 +1,45 @@ +# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to + +- minor_changes: + - Agent role - Enable registration for TLS and agent updates on remote sites. + - Agent role - Enable automatic activation of changes when needed for this role. Refer to the README for details. + - Agent role - RedHat - Only try to configure firewalld, if the systemd service is present. + +## Line Format + +# When writing a changelog entry, use the following format: + +# - scope - description starting with a uppercase letter and ending with a period at the very end. Multiple sentences are allowed. + +# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). + +# For changes that are not really scoped (for example, which affect a whole collection), use the following format: + +# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed. + +## Possible keys: + +# breaking_changes + +# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. +# major_changes + +# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. +# minor_changes + +# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. +# deprecated_features + +# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. +# removed_features + +# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. +# security_fixes + +# Fixes that address CVEs or resolve security concerns. Include links to CVE information. +# bugfixes + +# Fixes that resolve issues. +# known_issues + +# Known issues that are currently not fixed or will not be fixed. diff --git a/changelogs/fragments/playbooks.yml b/changelogs/fragments/playbooks.yml new file mode 100644 index 000000000..e8b57071b --- /dev/null +++ b/changelogs/fragments/playbooks.yml @@ -0,0 +1,3 @@ +# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to +- minor_changes: + - Playbooks - Add use case playbook for registering agents on remote sites. From 70234b9ca48c5fb98b45adbdc91391838c90f397 Mon Sep 17 00:00:00 2001 From: Robin Gierse Date: Fri, 20 Jan 2023 10:08:52 +0100 Subject: [PATCH 63/65] Bugfix changelogs. --- changelogs/fragments/agent.yml | 41 +----------------------------- changelogs/fragments/playbooks.yml | 2 +- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/changelogs/fragments/agent.yml b/changelogs/fragments/agent.yml index e936371a1..a4f8f4469 100644 --- a/changelogs/fragments/agent.yml +++ b/changelogs/fragments/agent.yml @@ -1,45 +1,6 @@ # https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to -- minor_changes: +minor_changes: - Agent role - Enable registration for TLS and agent updates on remote sites. - Agent role - Enable automatic activation of changes when needed for this role. Refer to the README for details. - Agent role - RedHat - Only try to configure firewalld, if the systemd service is present. - -## Line Format - -# When writing a changelog entry, use the following format: - -# - scope - description starting with a uppercase letter and ending with a period at the very end. Multiple sentences are allowed. - -# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). - -# For changes that are not really scoped (for example, which affect a whole collection), use the following format: - -# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed. - -## Possible keys: - -# breaking_changes - -# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. -# major_changes - -# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. -# minor_changes - -# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. -# deprecated_features - -# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. -# removed_features - -# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. -# security_fixes - -# Fixes that address CVEs or resolve security concerns. Include links to CVE information. -# bugfixes - -# Fixes that resolve issues. -# known_issues - -# Known issues that are currently not fixed or will not be fixed. diff --git a/changelogs/fragments/playbooks.yml b/changelogs/fragments/playbooks.yml index e8b57071b..c4bc45b60 100644 --- a/changelogs/fragments/playbooks.yml +++ b/changelogs/fragments/playbooks.yml @@ -1,3 +1,3 @@ # https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to -- minor_changes: +minor_changes: - Playbooks - Add use case playbook for registering agents on remote sites. From 55874906c8d165a3f8c6a75d6ca778b26418f145 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 20 Jan 2023 10:25:28 +0100 Subject: [PATCH 64/65] Changelog and documentation for the improved rule module. --- changelogs/fragments/bugfix_rule_module.yml | 2 +- changelogs/fragments/bugfix_rule_module_2.yml | 51 ------------------- plugins/modules/rule.py | 13 ++++- 3 files changed, 12 insertions(+), 54 deletions(-) delete mode 100644 changelogs/fragments/bugfix_rule_module_2.yml diff --git a/changelogs/fragments/bugfix_rule_module.yml b/changelogs/fragments/bugfix_rule_module.yml index 3b07abd42..d0f915bf6 100644 --- a/changelogs/fragments/bugfix_rule_module.yml +++ b/changelogs/fragments/bugfix_rule_module.yml @@ -1,6 +1,6 @@ # https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to bugfixes: - - Rule module - Now take the folder into account when checking for existing rules. + - Rule module - Now properly comparing the specified rule with the existing ones to achieve idempotency. # minor_changes: # - Add agent role. Currently supports the vanilla agent. diff --git a/changelogs/fragments/bugfix_rule_module_2.yml b/changelogs/fragments/bugfix_rule_module_2.yml deleted file mode 100644 index 3f867f181..000000000 --- a/changelogs/fragments/bugfix_rule_module_2.yml +++ /dev/null @@ -1,51 +0,0 @@ -# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to -bugfixes: - - Rule module - Compare the complete dicts, and not only their keys when checking for existing rules. - - Rule module - Currently, value_raw is not being checked when comparing rules. - -# minor_changes: -# - Add agent role. Currently supports the vanilla agent. - -# known_issues: -# - This release is still in development and a heavy work in progress. -# - Discovery module is not feature complete yet. -# - Downtime module is not fully idempotent yet. This affects service downtimes and deletions. - -## Line Format -# When writing a changelog entry, use the following format: - -# - scope - description starting with a lowercase letter and ending with a period at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). - -# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). - -# For changes that are not really scoped (for example, which affect a whole collection), use the following format: - -# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). - - -## Possible keys: - -# breaking_changes - -# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. -# major_changes - -# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. -# minor_changes - -# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. -# deprecated_features - -# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. -# removed_features - -# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. -# security_fixes - -# Fixes that address CVEs or resolve security concerns. Include links to CVE information. -# bugfixes - -# Fixes that resolve issues. -# known_issues - -# Known issues that are currently not fixed or will not be fixed. diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 90ff83e83..bde8153e9 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -20,8 +20,6 @@ description: - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. - - Currently, the idempotency of this module is restricted. - - To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. extends_documentation_fragment: [tribe29.checkmk.common] @@ -91,6 +89,17 @@ author: - diademiemi (@diademiemi) - Geoffroy Stévenne (@geof77) + +notes: + - To achieve idempotency, this module is comparing the specified rule with the already existing + rules based on conditions, folder, value_raw and enabled/disabled. + - To be able to compare the value_raw, which is internally stored in python format, the module + has to do a workaround: it is creating the specified rule, and then compares this rule with + all existing rules. + - Then, in case of I(state) == absent, it will delete both rules: the specified one and the duplicate + found. + - Or, in case of I(state) == present, it will delete the new rule, if a duplicate is already there, + and keep it if not. """ EXAMPLES = r""" From 683cff46831d0889bc06752b22b669a1efefd770 Mon Sep 17 00:00:00 2001 From: Lars Getwan Date: Fri, 20 Jan 2023 10:25:28 +0100 Subject: [PATCH 65/65] Changelog and documentation for the improved rule module. --- changelogs/fragments/bugfix_rule_module.yml | 7 +-- changelogs/fragments/bugfix_rule_module_2.yml | 51 ------------------- plugins/modules/rule.py | 15 +++++- 3 files changed, 17 insertions(+), 56 deletions(-) delete mode 100644 changelogs/fragments/bugfix_rule_module_2.yml diff --git a/changelogs/fragments/bugfix_rule_module.yml b/changelogs/fragments/bugfix_rule_module.yml index 3b07abd42..ebeb72326 100644 --- a/changelogs/fragments/bugfix_rule_module.yml +++ b/changelogs/fragments/bugfix_rule_module.yml @@ -1,12 +1,13 @@ # https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to bugfixes: - - Rule module - Now take the folder into account when checking for existing rules. + - Rule module - Now properly comparing the specified rule with the existing ones to achieve idempotency. # minor_changes: # - Add agent role. Currently supports the vanilla agent. -# known_issues: -# - This release is still in development and a heavy work in progress. +known_issues: + - Rule module - comparing the specified rule with the existing ones leads to additional changes in CMK's audit log + # - Discovery module is not feature complete yet. # - Downtime module is not fully idempotent yet. This affects service downtimes and deletions. diff --git a/changelogs/fragments/bugfix_rule_module_2.yml b/changelogs/fragments/bugfix_rule_module_2.yml deleted file mode 100644 index 3f867f181..000000000 --- a/changelogs/fragments/bugfix_rule_module_2.yml +++ /dev/null @@ -1,51 +0,0 @@ -# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to -bugfixes: - - Rule module - Compare the complete dicts, and not only their keys when checking for existing rules. - - Rule module - Currently, value_raw is not being checked when comparing rules. - -# minor_changes: -# - Add agent role. Currently supports the vanilla agent. - -# known_issues: -# - This release is still in development and a heavy work in progress. -# - Discovery module is not feature complete yet. -# - Downtime module is not fully idempotent yet. This affects service downtimes and deletions. - -## Line Format -# When writing a changelog entry, use the following format: - -# - scope - description starting with a lowercase letter and ending with a period at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). - -# The scope is usually a module or plugin name or group of modules or plugins, for example, lookup plugins. While module names can (and should) be mentioned directly (foo_module), plugin names should always be followed by the type (foo inventory plugin). - -# For changes that are not really scoped (for example, which affect a whole collection), use the following format: - -# - Description starting with an uppercase letter and ending with a dot at the very end. Multiple sentences are allowed (https://github.com/reference/to/an/issue or, if there is no issue, reference to a pull request itself). - - -## Possible keys: - -# breaking_changes - -# Changes that break existing playbooks or roles. This includes any change to existing behavior that forces users to update tasks. Displayed in both the changelogs and the Porting Guides. -# major_changes - -# Major changes to Ansible itself. Generally does not include module or plugin changes. Displayed in both the changelogs and the Porting Guides. -# minor_changes - -# Minor changes to Ansible, modules, or plugins. This includes new features, new parameters added to modules, or behavior changes to existing parameters. -# deprecated_features - -# Features that have been deprecated and are scheduled for removal in a future release. Displayed in both the changelogs and the Porting Guides. -# removed_features - -# Features that were previously deprecated and are now removed. Displayed in both the changelogs and the Porting Guides. -# security_fixes - -# Fixes that address CVEs or resolve security concerns. Include links to CVE information. -# bugfixes - -# Fixes that resolve issues. -# known_issues - -# Known issues that are currently not fixed or will not be fixed. diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 90ff83e83..1a1063f94 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -20,8 +20,6 @@ description: - Manage rules within Checkmk. Importing rules from the output of the Checkmk API. - Make sure these were exported with Checkmk 2.1.0p10 or above. See https://checkmk.com/werk/14670 for more information. - - Currently, the idempotency of this module is restricted. - - To check if an equal rule already exists, only folder, conditions and properties are used. value_raw is currently not being compared. extends_documentation_fragment: [tribe29.checkmk.common] @@ -91,6 +89,19 @@ author: - diademiemi (@diademiemi) - Geoffroy Stévenne (@geof77) + +notes: + - "To achieve idempotency, this module is comparing the specified rule with the already existing + rules based on conditions, folder, value_raw and enabled/disabled." + - "To be able to compare the value_raw, which is internally stored in python format, the module + has to do a workaround: it is creating the specified rule, and then compares this rule with + all existing rules." + - "Then, in case of I(state=absent), it will delete both rules: the specified one and the duplicate + found." + - "Or, in case of I(state=present), it will delete the new rule, if a duplicate is already there, + and keep it if not." + - "This obviously leads to more rules being added and removed as one might expect. That's also + visible in the pending changes and audit log." """ EXAMPLES = r"""