From abc435466d233e01780feaef502e8543f1a7afb3 Mon Sep 17 00:00:00 2001
From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com>
Date: Mon, 10 Mar 2025 06:31:05 +0100
Subject: [PATCH] [PR #9846/86dea88c backport][stable-9] dnf_versionlock: add
 support for Fedora 41 and dnf5 (#9864)

dnf_versionlock: add support for Fedora 41 and dnf5 (#9846)

Fixes: #9556

Signed-off-by: Abhijeet Kasurde <Akasurde@redhat.com>
(cherry picked from commit 86dea88cb6e6ced7c72ed622ddd71f63db276121)

Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
---
 changelogs/fragments/dnf_versionlock.yml      |  3 ++
 plugins/modules/dnf_versionlock.py            | 48 +++++++++++++++++--
 .../targets/dnf_versionlock/tasks/main.yml    |  3 +-
 3 files changed, 47 insertions(+), 7 deletions(-)
 create mode 100644 changelogs/fragments/dnf_versionlock.yml

diff --git a/changelogs/fragments/dnf_versionlock.yml b/changelogs/fragments/dnf_versionlock.yml
new file mode 100644
index 0000000000..35a53ff134
--- /dev/null
+++ b/changelogs/fragments/dnf_versionlock.yml
@@ -0,0 +1,3 @@
+---
+bugfixes:
+  - dnf_versionlock - add support for dnf5 (https://github.com/ansible-collections/community.general/issues/9556).
diff --git a/plugins/modules/dnf_versionlock.py b/plugins/modules/dnf_versionlock.py
index 3fcf132eaf..0d0708de51 100644
--- a/plugins/modules/dnf_versionlock.py
+++ b/plugins/modules/dnf_versionlock.py
@@ -235,6 +235,43 @@ def get_packages(module, patterns, only_installed=False):
     return packages_available_map_name_evrs
 
 
+def get_package_mgr():
+    for bin_path in (DNF_BIN,):
+        if os.path.exists(bin_path):
+            return "dnf5" if os.path.realpath(bin_path) == "/usr/bin/dnf5" else "dnf"
+    # fallback to dnf
+    return "dnf"
+
+
+def get_package_list(module, package_mgr="dnf"):
+    if package_mgr == "dnf":
+        return do_versionlock(module, "list").split()
+
+    package_list = []
+    if package_mgr == "dnf5":
+        stanza_start = False
+        package_name = None
+        for line in do_versionlock(module, "list").splitlines():
+            if line.startswith(("#", " ")):
+                continue
+            if line.startswith("Package name:"):
+                stanza_start = True
+                dummy, name = line.split(":", 1)
+                name = name.strip()
+                pkg_name = get_packages(module, patterns=[name])
+                package_name = "%s-%s.*" % (name, pkg_name[name].pop())
+                if package_name and package_name not in package_list:
+                    package_list.append(package_name)
+            if line.startswith("evr"):
+                dummy, package_version = line.split("=", 1)
+                package_version = package_version.strip()
+                if stanza_start:
+                    if package_name and package_name not in package_list:
+                        package_list.append(package_name)
+                    stanza_start = False
+    return package_list
+
+
 def main():
     module = AnsibleModule(
         argument_spec=dict(
@@ -253,9 +290,10 @@ def main():
     msg = ""
 
     # Check module pre-requisites.
-    if not os.path.exists(DNF_BIN):
-        module.fail_json(msg="%s was not found" % DNF_BIN)
-    if not os.path.exists(VERSIONLOCK_CONF):
+    global DNF_BIN
+    DNF_BIN = module.get_bin_path('dnf', True)
+    package_mgr = get_package_mgr()
+    if package_mgr == "dnf" and not os.path.exists(VERSIONLOCK_CONF):
         module.fail_json(msg="plugin versionlock is required")
 
     # Check incompatible options.
@@ -264,7 +302,7 @@ def main():
     if state != "clean" and not patterns:
         module.fail_json(msg="name list is required for %s state" % state)
 
-    locklist_pre = do_versionlock(module, "list").split()
+    locklist_pre = get_package_list(module, package_mgr=package_mgr)
 
     specs_toadd = []
     specs_todelete = []
@@ -343,7 +381,7 @@ def main():
         "specs_todelete": specs_todelete
     }
     if not module.check_mode:
-        response["locklist_post"] = do_versionlock(module, "list").split()
+        response["locklist_post"] = get_package_list(module, package_mgr=package_mgr)
     else:
         if state == "clean":
             response["locklist_post"] = []
diff --git a/tests/integration/targets/dnf_versionlock/tasks/main.yml b/tests/integration/targets/dnf_versionlock/tasks/main.yml
index f37bd8dd48..51e823ffd7 100644
--- a/tests/integration/targets/dnf_versionlock/tasks/main.yml
+++ b/tests/integration/targets/dnf_versionlock/tasks/main.yml
@@ -7,7 +7,6 @@
     - include_tasks: install.yml
     - include_tasks: lock_bash.yml
     - include_tasks: lock_updates.yml
-  when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=') and ansible_distribution_major_version is version('41', '<')) or
+  when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or
         (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
-# TODO: Fix on Fedora 41, apparently the output changed!
 ...