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 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/Vagrantfile b/Vagrantfile index 314f05dd9..7a6f71075 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 @@ -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 diff --git a/changelogs/fragments/agent.yml b/changelogs/fragments/agent.yml new file mode 100644 index 000000000..a4f8f4469 --- /dev/null +++ b/changelogs/fragments/agent.yml @@ -0,0 +1,6 @@ +# 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. diff --git a/changelogs/fragments/bugfix_rule_module.yml b/changelogs/fragments/bugfix_rule_module.yml new file mode 100644 index 000000000..ebeb72326 --- /dev/null +++ b/changelogs/fragments/bugfix_rule_module.yml @@ -0,0 +1,51 @@ +# https://docs.ansible.com/ansible/latest/community/development_process.html#changelogs-how-to +bugfixes: + - 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: + - 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. + +## 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/playbooks.yml b/changelogs/fragments/playbooks.yml new file mode 100644 index 000000000..c4bc45b60 --- /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. 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. diff --git a/galaxy.yml b/galaxy.yml index 375a67625..4ce5c00d3 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 @@ -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 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. diff --git a/playbooks/usecases/remote-registration.yml b/playbooks/usecases/remote-registration.yml new file mode 100644 index 000000000..0922e059e --- /dev/null +++ b/playbooks/usecases/remote-registration.yml @@ -0,0 +1,47 @@ +--- +# 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. + 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: 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: 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_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 and changes activated in any case. + checkmk_agent_auto_activate: 'true' + 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 diff --git a/plugins/modules/rule.py b/plugins/modules/rule.py index 3d70dc891..1a1063f94 100644 --- a/plugins/modules/rule.py +++ b/plugins/modules/rule.py @@ -18,8 +18,8 @@ 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. + - 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. extends_documentation_fragment: [tribe29.checkmk.common] @@ -28,6 +28,54 @@ description: Definition of the rule as returned by the Checkmk API. required: true type: dict + suboptions: + location: + description: + - Location of the rule within a folder. + - By default rules are created at the bottom of the "/" folder. + - Mutually exclusive with I(folder). + type: dict + suboptions: + position: + description: + - Position of the rule in the folder. + - Has no effect when I(state=absent). + 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(folder). + type: str + folder: + description: + - Folder of the rule. + - Required when I(position) is C(top) or C(bottom). + - Required when I(state=absent). + - Mutually exclusive with I(rule_id). + default: "/" + type: str + folder: + description: + - Folder of the rule. + - 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 @@ -40,10 +88,25 @@ 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""" -# Create a rule in checkgroup_parameters:memory_percentage_used. +# Create a rule in checkgroup_parameters:memory_percentage_used +# at the top of the main folder. - name: "Create a rule in checkgroup_parameters:memory_percentage_used." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -69,10 +132,52 @@ "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)}" + location: + folder: "/" + position: "top" state: "present" + register: response -# Delete first rule in this ruleset. +- name: Show the ID of the new rule + debug: + msg: "RULE ID : {{ response.id }}" + +# 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/" + site: "my_site" + automation_user: "automation" + automation_secret: "$SECRET" + ruleset: "checkgroup_parameters:memory_percentage_used" + 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)}" + location: + position: "after" + rule_id: "{{ response.id }}" + state: "present" + +# Delete the first rule. - name: "Delete a rule." tribe29.checkmk.rule: server_url: "http://localhost/" @@ -104,10 +209,16 @@ 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.' + +id: + description: The ID of the rule. + type: str + returned: when the rule is created or when it already exists + sample: '1f97bc43-52dc-4f1a-ab7b-c2e9553958ab' """ import json @@ -121,18 +232,18 @@ from urllib.parse import urlencode -def exit_failed(module, msg): - result = {"msg": msg, "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): - result = {"msg": msg, "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): - result = {"msg": msg, "changed": False, "failed": False} +def exit_ok(module, msg, id=""): + result = {"msg": msg, "id": id, "changed": False, "failed": False} module.exit_json(**result) @@ -155,27 +266,32 @@ 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")) 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"]) - and sorted(r["extensions"]["value_raw"]) == sorted(rule["value_raw"]) + r["id"] != rule["id"] + and r["extensions"]["conditions"] == rule["extensions"]["conditions"] + 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"] ): # 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 = { @@ -199,8 +315,51 @@ def create_rule(module, base_url, headers, ruleset, rule): % (info["status"], info["body"]), ) + r = json.loads(response.read().decode("utf-8")) + + return r + + +def create_rule(module, base_url, headers, ruleset, rule): + + created = True + + # get API representation of the rule + r = get_api_repr(module, base_url, headers, ruleset, rule) + + # compare the API output to existing rules + e = get_existing_rule(module, base_url, headers, ruleset, r) + + # 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"], not created) + + # else return new rule id + return (r["id"], created) + + +def delete_rule(module, base_url, headers, ruleset, rule): -def delete_rule(module, base_url, headers, rule_id): + deleted = True + + # get API representation of the rule + r = get_api_repr(module, base_url, headers, ruleset, rule) + + # compare the API output to existing rules + e = get_existing_rule(module, base_url, headers, ruleset, r) + + # 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 deleted + else: + delete_rule_by_id(module, base_url, headers, r["id"]) + return not deleted + + +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) @@ -215,6 +374,58 @@ def delete_rule(module, base_url, headers, 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"]), + ) + return info["etag"] + + +def move_rule(module, base_url, headers, rule_id, location): + api_endpoint = "/objects/rule/" + rule_id + "/actions/move/invoke" + + api_keywords = { + "top": "top_of_folder", + "bottom": "bottom_of_folder", + "before": "before_specific_rule", + "after": "after_specific_rule", + } + + params = { + "position": api_keywords[location["position"]], + } + if location["position"] in ["after", "before"]: + params["rule_id"] = location["rule_id"] + else: + params["folder"] = location["folder"] + + headers["If-Match"] = get_rule_etag(module, base_url, headers, rule_id) + + url = base_url + api_endpoint + + 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"]), + ) + + r = json.loads(response.read().decode("utf-8")) + + def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( @@ -224,7 +435,47 @@ 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), + 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, + deprecated_aliases=[ + dict( + name="folder", + collection_name="tribe29.checkmk", + version="1.0.0", + ), + ], + ), + ), + mutually_exclusive=[("folder", "location")], + ), state=dict(type="str", default="present", choices=["present", "absent"]), ) @@ -249,10 +500,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") == "": @@ -264,26 +516,26 @@ 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) - # 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) - exit_changed(module, "Deleted rule") - # If state is present, do nothing - else: - exit_ok(module, "Rule already exists") - # If rule does not exist - else: - # If state is present, create the rule - if module.params.get("state") == "present": - create_rule(module, base_url, headers, ruleset, rule) - exit_changed(module, "Created rule") + 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") + + # 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, "Rule deleted") else: - # If state is absent, do nothing - 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) + 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, "Rule created", rule_id) + exit_ok(module, "Rule already exists", rule_id) # Fallback exit_failed(module, "Unknown error") diff --git a/requirements.txt b/requirements.txt index e434c1bc0..bb932675d 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 >= 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 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/agent/README.md b/roles/agent/README.md index 45bc5c1ff..fd471f8c7 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 site 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. @@ -60,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. @@ -149,6 +162,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 6809393a1..4ee53a1f6 100644 --- a/roles/agent/defaults/main.yml +++ b/roles/agent/defaults/main.yml @@ -1,17 +1,20 @@ --- -checkmk_agent_version: "2.1.0p18" +checkmk_agent_version: "2.1.0p19" 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: # 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' @@ -22,7 +25,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/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 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 ae5276524..8d0480991 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: | @@ -63,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: @@ -78,7 +82,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_registration_site }} -p {{ checkmk_agent_protocol }} \ -U {{ checkmk_agent_user }} -P {{ checkmk_agent_auth }} register: checkmk_agent_update_state when: | @@ -94,7 +98,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_registration_site }} -p {{ checkmk_agent_protocol }} \ -U {{ checkmk_agent_user }} -S {{ checkmk_agent_auth }} register: checkmk_agent_update_state when: | @@ -105,18 +109,19 @@ 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: | 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_registration_site }} \ -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) @@ -131,3 +136,4 @@ state: "fix_all" delegate_to: "{{ checkmk_agent_delegate_api_calls }}" when: checkmk_agent_discover | bool + notify: "activate changes" 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..3f1fc5a59 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" @@ -31,10 +31,6 @@ checkmk_rules: rule: conditions: { "host_labels": [], - "host_name": { - "match_on": [], - "operator": "one_of" - }, "host_tags": [], "service_labels": [] } 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"