From 1f2e60f65d78326deea3e221ac6ec010e1f49813 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Wed, 29 Apr 2026 21:56:30 +1200 Subject: [PATCH] lxc_container: use LVM runners from `_lvm` module utils (#11920) * lxc_container: use LVM runners from _lvm module utils Replace direct run_command calls for lvs, vgdisplay, lvdisplay, lvcreate, and lvremove with the shared CmdRunner-based runners from module_utils/_lvm.py. Switches from human-readable text parsing to machine-readable --noheadings/--nosuffix/--separator output. Co-Authored-By: Claude Sonnet 4.6 * lxc_container: add changelog fragment for PR 11920 Co-Authored-By: Claude Sonnet 4.6 * generate run_info information for commands --------- Co-authored-by: Claude Sonnet 4.6 --- .../11920-lxc-container-use-lvm-cmdrunner.yml | 2 + plugins/module_utils/_lvm.py | 4 +- plugins/modules/lxc_container.py | 80 ++++++++++--------- 3 files changed, 45 insertions(+), 41 deletions(-) create mode 100644 changelogs/fragments/11920-lxc-container-use-lvm-cmdrunner.yml diff --git a/changelogs/fragments/11920-lxc-container-use-lvm-cmdrunner.yml b/changelogs/fragments/11920-lxc-container-use-lvm-cmdrunner.yml new file mode 100644 index 0000000000..143bc4b497 --- /dev/null +++ b/changelogs/fragments/11920-lxc-container-use-lvm-cmdrunner.yml @@ -0,0 +1,2 @@ +minor_changes: + - lxc_container - use shared LVM runners from ``_lvm`` module utils instead of direct ``run_command`` calls for LVM2 commands (https://github.com/ansible-collections/community.general/pull/11920). diff --git a/plugins/module_utils/_lvm.py b/plugins/module_utils/_lvm.py index 7a09c97aa4..6509167c7e 100644 --- a/plugins/module_utils/_lvm.py +++ b/plugins/module_utils/_lvm.py @@ -179,7 +179,7 @@ def pvmove_runner(module: AnsibleModule, **kwargs) -> CmdRunner: def vgs_runner(module: AnsibleModule, **kwargs) -> CmdRunner: """ Runner for C(vgs). Used by: community.general.lvol, community.general.lvg, - community.general.lvg_rename. + community.general.lvg_rename, community.general.lxc_container. Suggested arg_formats keys: noheadings nosuffix readonly units separator fields select vg """ @@ -341,7 +341,7 @@ def vgrename_runner(module: AnsibleModule, **kwargs) -> CmdRunner: def lvs_runner(module: AnsibleModule, **kwargs) -> CmdRunner: """ - Runner for C(lvs). Used by: community.general.lvol. + Runner for C(lvs). Used by: community.general.lvol, community.general.lxc_container. Suggested arg_formats keys: all noheadings nosuffix units separator fields select vg diff --git a/plugins/modules/lxc_container.py b/plugins/modules/lxc_container.py index d79b6fa0ae..d59acbfb29 100644 --- a/plugins/modules/lxc_container.py +++ b/plugins/modules/lxc_container.py @@ -422,6 +422,12 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE +from ansible_collections.community.general.plugins.module_utils._lvm import ( + lvcreate_runner, + lvremove_runner, + lvs_runner, + vgs_runner, +) from ansible_collections.community.general.plugins.module_utils._lxc import create_script # LXC_COMPRESSION_MAP is a map of available compression types when creating @@ -519,6 +525,7 @@ class LxcContainerManagement: self.container = self.get_container_bind() self.archive_info = None self.clone_info = None + self.run_info = [] def get_container_bind(self): return lxc.Container(name=self.container_name) @@ -1045,13 +1052,13 @@ class LxcContainerManagement: """Return a list of all lv in a current vg.""" vg = self._get_lxc_vg() - build_command = [self.module.get_bin_path("lvs", True)] - rc, stdout, err = self.module.run_command(build_command) + with lvs_runner(self.module)("noheadings separator fields vg") as ctx: + rc, stdout, err = ctx.run(separator=";", fields="lv_name", vg=[vg]) + if self.module._verbosity >= 4: + self.run_info.append(ctx.run_info) if rc != 0: - self.failure(err=err, rc=rc, msg="Failed to get list of LVs", command=" ".join(build_command)) - - all_lvms = [i.split() for i in stdout.splitlines()][1:] - return [lv_entry[0] for lv_entry in all_lvms if lv_entry[1] == vg] + self.failure(err=err, rc=rc, msg="Failed to get list of LVs") + return [line.strip() for line in stdout.splitlines() if line.strip()] def _get_vg_free_pe(self, vg_name): """Return the available size of a given VG. @@ -1062,15 +1069,13 @@ class LxcContainerManagement: :type: ``tuple`` """ - build_command = ["vgdisplay", vg_name, "--units", "g"] - rc, stdout, err = self.module.run_command(build_command) + with vgs_runner(self.module)("noheadings nosuffix units separator fields vg") as ctx: + rc, stdout, err = ctx.run(units="g", separator=";", fields="vg_free", vg=[vg_name]) + if self.module._verbosity >= 4: + self.run_info.append(ctx.run_info) if rc != 0: - self.failure(err=err, rc=rc, msg=f"failed to read vg {vg_name}", command=" ".join(build_command)) - - vg_info = [i.strip() for i in stdout.splitlines()][1:] - free_pe = [i for i in vg_info if i.startswith("Free")] - _free_pe = free_pe[0].split() - return float(_free_pe[-2]), _free_pe[-1] + self.failure(err=err, rc=rc, msg=f"failed to read vg {vg_name}") + return float(stdout.strip()), "g" def _get_lv_size(self, lv_name): """Return the available size of a given LV. @@ -1083,15 +1088,13 @@ class LxcContainerManagement: vg = self._get_lxc_vg() lv = os.path.join(vg, lv_name) - build_command = ["lvdisplay", lv, "--units", "g"] - rc, stdout, err = self.module.run_command(build_command) + with lvs_runner(self.module)("noheadings nosuffix units separator fields vg") as ctx: + rc, stdout, err = ctx.run(units="g", separator=";", fields="lv_size", vg=[lv]) + if self.module._verbosity >= 4: + self.run_info.append(ctx.run_info) if rc != 0: - self.failure(err=err, rc=rc, msg=f"failed to read lv {lv}", command=" ".join(build_command)) - - lv_info = [i.strip() for i in stdout.splitlines()][1:] - _free_pe = [i for i in lv_info if i.startswith("LV Size")] - free_pe = _free_pe[0].split() - return self._roundup(float(free_pe[-2])), free_pe[-1] + self.failure(err=err, rc=rc, msg=f"failed to read lv {lv}") + return self._roundup(float(stdout.strip())), "g" def _lvm_snapshot_create(self, source_lv, snapshot_name, snapshot_size_gb=5): """Create an LVM snapshot. @@ -1113,16 +1116,15 @@ class LxcContainerManagement: ) self.failure(error="Not enough space to create snapshot", rc=2, msg=message) - # Create LVM Snapshot - build_command = [ - self.module.get_bin_path("lvcreate", True), - "-n", - snapshot_name, - "-s", - os.path.join(vg, source_lv), - f"-L{snapshot_size_gb}g", - ] - rc, stdout, err = self.module.run_command(build_command) + with lvcreate_runner(self.module)("size_L is_snapshot lv vg") as ctx: + rc, dummy, err = ctx.run( + size_L=f"{snapshot_size_gb}g", + is_snapshot=True, + lv=[snapshot_name], + vg=[os.path.join(vg, source_lv)], + ) + if self.module._verbosity >= 4: + self.run_info.append(ctx.run_info) if rc != 0: self.failure(err=err, rc=rc, msg=f"Failed to Create LVM snapshot {vg}/{source_lv} --> {snapshot_name}") @@ -1190,14 +1192,12 @@ class LxcContainerManagement: """ vg = self._get_lxc_vg() - build_command = [ - self.module.get_bin_path("lvremove", True), - "-f", - f"{vg}/{lv_name}", - ] - rc, stdout, err = self.module.run_command(build_command) + with lvremove_runner(self.module)("force lv") as ctx: + rc, dummy, err = ctx.run(force=True, lv=[f"{vg}/{lv_name}"]) + if self.module._verbosity >= 4: + self.run_info.append(ctx.run_info) if rc != 0: - self.failure(err=err, rc=rc, msg=f"Failed to remove LVM LV {vg}/{lv_name}", command=" ".join(build_command)) + self.failure(err=err, rc=rc, msg=f"Failed to remove LVM LV {vg}/{lv_name}") def _rsync_data(self, container_path, temp_dir): """Sync the container directory to the temp directory. @@ -1375,6 +1375,8 @@ class LxcContainerManagement: :param rc: ``int`` Return code while executing an Ansible command. :param msg: ``str`` Message to report. """ + if self.run_info: + kwargs["run_info"] = self.run_info self.module.fail_json(**kwargs)