diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index fac3fae8f8..9b897bfe1c 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -1058,6 +1058,8 @@ files: maintainers: fraff $modules/pacemaker_cluster.py: maintainers: matbu munchtoast + $modules/pacemaker_info.py: + maintainers: munchtoast $modules/pacemaker_resource.py: maintainers: munchtoast $modules/packet_: diff --git a/plugins/module_utils/pacemaker.py b/plugins/module_utils/pacemaker.py index f0f54cce9d..6228244e10 100644 --- a/plugins/module_utils/pacemaker.py +++ b/plugins/module_utils/pacemaker.py @@ -67,6 +67,8 @@ def pacemaker_runner(module, **kwargs): wait=cmd_runner_fmt.as_opt_eq_val("--wait"), config=cmd_runner_fmt.as_fixed("config"), force=cmd_runner_fmt.as_bool("--force"), + version=cmd_runner_fmt.as_fixed("--version"), + output_format=cmd_runner_fmt.as_opt_eq_val("--output-format"), ), **kwargs ) diff --git a/plugins/modules/pacemaker_info.py b/plugins/modules/pacemaker_info.py new file mode 100644 index 0000000000..1ca16d1439 --- /dev/null +++ b/plugins/modules/pacemaker_info.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Dexter Le +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r""" +module: pacemaker_info +short_description: Gather information about pacemaker cluster +author: + - Dexter Le (@munchtoast) +version_added: 11.1.0 +description: + - Gather information about the cluster +extends_documentation_fragment: + - community.general.attributes + - community.general.attributes.info_module +""" + +EXAMPLES = r""" +- name: Gather pacemaker cluster info + community.general.pacemaker_info: + register: result + +- name: Debug cluster info + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r""" +version: + description: Pacemaker CLI version + returned: always + type: str +cluster_info: + description: Cluster information such as the name, uuid, and nodes. + returned: always + type: dict +resource_info: + description: All resources available on the cluster and their status. + returned: success + type: dict +stonith_info: + description: All STONITH information on the cluster. + returned: success + type: dict +constraint_info: + description: All cluster resource constraints on the cluster. + returned: success + type: dict +property_info: + description: All properties present on the cluster. + returned: success + type: dict +""" + +import json + +from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper +from ansible_collections.community.general.plugins.module_utils.pacemaker import pacemaker_runner + + +class PacemakerInfo(ModuleHelper): + module = dict( + argument_spec=dict(), + supports_check_mode=True, + ) + info_vars = { + "cluster_info": "cluster", + "resource_info": "resource", + "stonith_info": "stonith", + "constraint_info": "constraint", + "property_info": "property" + } + output_params = info_vars.keys() + + def __init_module__(self): + self.runner = pacemaker_runner(self.module) + with self.runner("version") as ctx: + rc, out, err = ctx.run() + self.vars.version = out.strip() + + def _process_command_output(self, fail_on_err, ignore_err_msg="", cli_action=""): + def process(rc, out, err): + if fail_on_err and rc != 0 and err and ignore_err_msg not in err: + self.do_raise('pcs {0} config failed with error (rc={1}): {2}'.format(cli_action, rc, err)) + out = json.loads(out) + return None if out == "" else out + return process + + def _get_info(self, cli_action): + with self.runner("cli_action config output_format", output_process=self._process_command_output(True, "Fail", cli_action)) as ctx: + return ctx.run(cli_action=cli_action, output_format="json") + + def __run__(self): + for key, cli_action in sorted(self.info_vars.items()): + self.vars.set(key, self._get_info(cli_action)) + + +def main(): + PacemakerInfo.execute() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/plugins/modules/test_pacemaker_info.py b/tests/unit/plugins/modules/test_pacemaker_info.py new file mode 100644 index 0000000000..6be6b4ec3a --- /dev/null +++ b/tests/unit/plugins/modules/test_pacemaker_info.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Author: Dexter Le (dextersydney2001@gmail.com) +# Largely adapted from test_redhat_subscription by +# Jiri Hnidek (jhnidek@redhat.com) +# +# Copyright (c) Dexter Le (dextersydney2001@gmail.com) +# Copyright (c) Jiri Hnidek (jhnidek@redhat.com) +# +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible_collections.community.general.plugins.modules import pacemaker_info +from .uthelper import UTHelper, RunCommandMock + +UTHelper.from_module(pacemaker_info, __name__, mocks=[RunCommandMock]) diff --git a/tests/unit/plugins/modules/test_pacemaker_info.yaml b/tests/unit/plugins/modules/test_pacemaker_info.yaml new file mode 100644 index 0000000000..6e83131cf3 --- /dev/null +++ b/tests/unit/plugins/modules/test_pacemaker_info.yaml @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Dexter Le (dextersydney2001@gmail.com) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +--- +anchors: + environ: &env-def {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false} +test_cases: + - id: test_info_initial_offline + input: {} + output: + failed: true + msg: "pcs resource config failed with error (rc=1): Error: unable to get cib" + version: "0.10.0" + cluster_info: + cluster_name: test_cluster + cluster_uuid: test_uuid + mocks: + run_command: + - command: [/testbin/pcs, --version] + environ: *env-def + rc: 0 + out: "0.10.0" + err: "" + - command: [/testbin/pcs, cluster, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"cluster_name": "test_cluster", "cluster_uuid": "test_uuid"}' + err: "" + - command: [/testbin/pcs, constraint, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"location": [{"attributes": {"constraint_id": "test-constraint-id", "node": "example.com", "score": "-INFINITY"}, "resource_id": "test-resource-id", "role": "Started"}]}' + err: "" + - command: [/testbin/pcs, property, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"nvsets": [{"id": "test-bootstrap", "name": "test-infra", "value": "corosync"}]}' + err: "" + - command: [/testbin/pcs, resource, config, --output-format=json] + environ: *env-def + rc: 1 + out: "" + err: "Error: unable to get cib" + - id: test_info_initial_online + input: {} + output: + version: "0.10.0" + cluster_info: + cluster_name: test_cluster + cluster_uuid: test_uuid + resource_info: + clones: + - description: null + id: test-clone + member_id: test-member-0 + stonith_info: + primitives: + - agent_name: + provider: null + standard: stonith + type: fence_aws + description: null + id: test-stonith + constraint_info: + location: + - attributes: + constraint_id: test-constraint-id + node: example.com + score: -INFINITY + resource_id: test-resource-id + role: Started + property_info: + nvsets: + - id: test-bootstrap + name: test-infra + value: corosync + mocks: + run_command: + - command: [/testbin/pcs, --version] + environ: *env-def + rc: 0 + out: "0.10.0" + err: "" + - command: [/testbin/pcs, cluster, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"cluster_name": "test_cluster", "cluster_uuid": "test_uuid"}' + err: "" + - command: [/testbin/pcs, constraint, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"location": [{"attributes": {"constraint_id": "test-constraint-id", "node": "example.com", "score": "-INFINITY"}, "resource_id": "test-resource-id", "role": "Started"}]}' + err: "" + - command: [/testbin/pcs, property, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"nvsets": [{"id": "test-bootstrap", "name": "test-infra", "value": "corosync"}]}' + err: "" + - command: [/testbin/pcs, resource, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"clones": [{"description": null, "id": "test-clone", "member_id": "test-member-0"}]}' + err: "" + - command: [/testbin/pcs, stonith, config, --output-format=json] + environ: *env-def + rc: 0 + out: '{"primitives": [{"agent_name": {"provider": null, "standard": "stonith", "type": "fence_aws"}, "description": null, "id": "test-stonith"}]}' + err: ""