Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/67975.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve support for installing via groups with dnf5 on Fedora 41/42
63 changes: 59 additions & 4 deletions salt/modules/yumpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2512,6 +2512,28 @@ def group_list():
"available language groups:": "available languages",
}

if _yum() == "dnf5":
out = __salt__["cmd.run_stdout"](
[_yum(), "group", "list", "--hidden"],
output_loglevel="trace",
python_shell=False,
)

for line in salt.utils.itertools.split(out, "\n"):
line_lc = line.lower()
# split line into 3 parts: ID (no spaces), Name (contains spaces), and
# Installed (one of 'yes' or 'no')
match = re.match(r"^(\S+?)\s+(.+?)\s*(yes|no)$", line_lc)
if match:
pkg_id, pkg_name, pkg_installed = match.groups()
if pkg_id != "id":
if pkg_installed == "yes":
ret["installed"].append(pkg_id)
else:
ret["available"].append(pkg_id)
return ret

# else: not dnf5
out = __salt__["cmd.run_stdout"](
[_yum(), "grouplist", "hidden"], output_loglevel="trace", python_shell=False
)
Expand Down Expand Up @@ -2615,7 +2637,10 @@ def group_info(name, expand=False, ignore_groups=None, **kwargs):
}
)

cmd = [_yum(), "--quiet"] + options + ["groupinfo", name]
if _yum() == "dnf5":
cmd = [_yum(), "--quiet"] + options + ["group", "info", name]
else:
cmd = [_yum(), "--quiet"] + options + ["groupinfo", name]
out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False)

g_info = {}
Expand All @@ -2630,9 +2655,15 @@ def group_info(name, expand=False, ignore_groups=None, **kwargs):
ret["type"] = "environment group"
elif "group" in g_info:
ret["type"] = "package group"
elif "name" in g_info:
ret["type"] = "package group"

ret["group"] = g_info.get("environment group") or g_info.get("group")
ret["id"] = g_info.get("environment-id") or g_info.get("group-id")
ret["group"] = (
g_info.get("environment group") or g_info.get("group") or g_info.get("name")
)
ret["id"] = (
g_info.get("environment-id") or g_info.get("group-id") or g_info.get("id")
)
if not ret["group"] and not ret["id"]:
raise CommandExecutionError(f"Group '{name}' not found")

Expand All @@ -2643,7 +2674,8 @@ def group_info(name, expand=False, ignore_groups=None, **kwargs):
for pkgtype in pkgtypes:
target_found = False
for line in salt.utils.itertools.split(out, "\n"):
line = line.strip().lstrip(string.punctuation)
line = line.strip().lstrip(string.punctuation).lstrip()
# dnf
match = re.match(
pkgtypes_capturegroup + r" (?:groups|packages):\s*$", line.lower()
)
Expand All @@ -2656,6 +2688,29 @@ def group_info(name, expand=False, ignore_groups=None, **kwargs):
# We've reached the targeted section
target_found = True
continue
# dnf5
match_dnf5 = re.match(
pkgtypes_capturegroup + r" (?:groups|packages)\s*:\s*(.*?)$",
line.lower(),
)
if match_dnf5:
if target_found:
# We've reached a new section, break from loop
break
else:
if match_dnf5.group(1) == pkgtype:
# We've reached the targeted section
target_found = True
# The difference here from dnf (above) is that this line
# also contains the first package of this section.
# Have to pull out the package name, but not changing the case
rematch_dnf5 = re.match(r"^.*:\s*(.*?)$", line)
# Let line be the match we found, and then simply
# continue on, where we'll add this to the appropriate group
# Can't fail...
if rematch_dnf5:
line = rematch_dnf5.group(1)

if target_found:
if expand and ret["type"] == "environment group":
if not line or line in completed_groups:
Expand Down
77 changes: 77 additions & 0 deletions tests/pytests/unit/modules/test_yumpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3256,3 +3256,80 @@ def test_normalize_name_with_arch_x86_64_v2():
assert yumpkg.normalize_name("chrony.x86_64") == "chrony.x86_64"
with patch("salt.utils.pkg.rpm.get_osarch", MagicMock(return_value="x86_64")):
assert yumpkg.normalize_name("rootfiles.noarch") == "rootfiles"


def test_67975_dnf5_group_info():
"""
Test yumpkg.group_info parsing for dnf5 format
"""
patch_yum = patch("salt.modules.yumpkg._yum", Mock(return_value="dnf5"))
expected = {
"mandatory": [
"libreoffice-calc",
"libreoffice-emailmerge",
"libreoffice-graphicfilter",
"libreoffice-impress",
"libreoffice-writer",
],
"optional": [
"libreoffice-base",
"libreoffice-draw",
"libreoffice-math",
"libreoffice-pyuno",
],
"default": [],
"conditional": [],
"type": "package group",
"group": "LibreOffice",
"id": "libreoffice",
"description": "LibreOffice Productivity Suite",
}
cmd_out = """Id : libreoffice
Name : LibreOffice
Description : LibreOffice Productivity Suite
Installed : yes
Order :
Langonly :
Uservisible : yes
Repositories : @System
Mandatory packages : libreoffice-calc
: libreoffice-emailmerge
: libreoffice-graphicfilter
: libreoffice-impress
: libreoffice-writer
Optional packages : libreoffice-base
: libreoffice-draw
: libreoffice-math
: libreoffice-pyuno"""
with patch_yum:
with patch.dict(
yumpkg.__salt__, {"cmd.run_stdout": MagicMock(return_value=cmd_out)}
):
info = yumpkg.group_info("libreoffice")
assert info == expected


def test_67975_dnf5_group_list():
patch_yum = patch("salt.modules.yumpkg._yum", Mock(return_value="dnf5"))
mock_out = MagicMock(
return_value="""\
ID Name Installed
foo Foo package no
bar Bar package no
brackets Just (testing) yes yes
cleaners Mop and bucket yes
last But not least no\
"""
)
patch_grplist = patch.dict(yumpkg.__salt__, {"cmd.run_stdout": mock_out})
with patch_yum:
with patch_grplist:
result = yumpkg.group_list()
expected = {
"installed": ["brackets", "cleaners"],
"available": ["foo", "bar"],
"installed environments": [],
"available environments": [],
"available languages": {},
}
assert result == expected
Loading