Skip to content

Revert "DNF4 to DNF5" - #130

Merged
yselkowitz merged 1 commit into
masterfrom
revert-121-dnf5-cleanup
Aug 10, 2026
Merged

Revert "DNF4 to DNF5"#130
yselkowitz merged 1 commit into
masterfrom
revert-121-dnf5-cleanup

Conversation

@yselkowitz

@yselkowitz yselkowitz commented Aug 10, 2026

Copy link
Copy Markdown
Member

Reverts #121

Summary by CodeRabbit

  • New Features

    • Updated container and automated workflow setup for improved Python package compatibility.
  • Bug Fixes

    • Improved generated page naming behavior.
    • Updated configuration and historic-data messaging for clearer output.
  • Documentation

    • Streamlined the README by removing outdated process and structure sections and correcting terminology.
  • Refactor

    • Simplified dependency handling and removed obsolete support components.

@yselkowitz
yselkowitz requested a review from bhoy-troy August 10, 2026 16:19
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR replaces DNF5 packages with python3-dnf, removes the DNF5 helper, changes string formatting across configuration, historic-data, and page-generation code, updates exception handling, and removes several README sections.

Changes

Runtime dependency and cleanup

Layer / File(s) Summary
Package and helper changes
.github/workflows/*, Dockerfile, content_resolver/utils.py, README.md
Workflows and the image use python3-dnf instead of python3-libdnf5. The dnf5_base() helper and selected README content are removed.

Configuration loading

Layer / File(s) Summary
Configuration loader updates
content_resolver/config_manager.py
Messages use .format(). Several handlers stop exception chaining. Repository base-URL and JSON-loading handlers reference undefined variables.

Historic data

Layer / File(s) Summary
Historic-data updates
content_resolver/historia_data.py
Filenames, logs, chart names, and dataset selection use the revised formatting and conditional construction.

Page generation

Layer / File(s) Summary
Page-generation updates
content_resolver/page_generation.py
Page-name interpolation uses .format(). Generated HTML filenames no longer strip directory components with os.path.basename.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: bhoy-troy

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states that the pull request reverts the DNF4 to DNF5 change, matching the stated objective and changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch revert-121-dnf5-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.


return historic_data

log(" Done!")
"arch": arch,
}
page_name = f"view--{view_conf_id}--{arch}"
page_name = "view--{view_conf_id}--{arch}".format(
json_data = self.load_data(os.path.join(directory, json_file))
except Exception as err:
raise ConfigError(f"Error loading a JSON data file '{json_file}': {err}") from err
except:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
Dockerfile (1)

3-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Preserve the minimal-install settings unless the larger image is intentional.

Lines 3-4 remove tsflags=nodocs and install_weak_deps=False. DNF documents weak dependencies as Recommends or Supplements, and nodocs as the RPM no-documentation flag. Without these controls, the image can include more packages and files than the runtime needs. (dnf.readthedocs.io)

If the image must remain minimal, restore the settings and compare the image size and package list before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 3 - 4, Restore the minimal-install controls on the
DNF update and install commands in the Dockerfile by reapplying tsflags=nodocs
and install_weak_deps=False. Keep the existing package installation unchanged
and verify the resulting image size and package list remain minimal.
content_resolver/config_manager.py (1)

864-866: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve the YAML parser cause explicitly.

This handler catches yaml.YAMLError as err but raises ConfigError without from err. Raise the wrapper from the caught exception so error reports retain an explicit cause.

Proposed fix
-                        raise ConfigError("Error loading a config '{filename}': {err}".format(
-                                    filename=yml_file,
-                                    err=err))
+                        raise ConfigError("Error loading a config '{filename}': {err}".format(
+                                    filename=yml_file,
+                                    err=err)) from err
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content_resolver/config_manager.py` around lines 864 - 866, Update the YAML
error handler around the ConfigError raise to explicitly chain the caught
yaml.YAMLError as the cause using the existing err variable, while preserving
the current message and yml_file context.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@content_resolver/config_manager.py`:
- Around line 123-127: Update the KeyError handler in _load_config_repo_v2 to
format ConfigError with the available document_id value instead of the undefined
yml_file, preserving the existing missing-baseurl error message behavior.
- Around line 973-976: Update the exception handler around self.load_data(...)
to bind the caught error and replace the bare except with the appropriate
specific exceptions, or except Exception as err if ordinary failures must be
covered. Preserve the ConfigError message while chaining the original exception
so the JSON file failure is reported without raising NameError.

In `@content_resolver/historia_data.py`:
- Around line 132-135: In _read_historic_data, move the “Done!” and blank-line
log calls before return historic_data so both messages execute while preserving
the returned historic_data value.

In `@content_resolver/page_generation.py`:
- Around line 27-29: Update filename construction in the page-generation flow to
reject page_name values containing path separators or traversal components, and
verify the resolved output path remains within output before writing. Preserve
valid filename generation while preventing nested-path failures and writes
outside the output directory; use the existing basename containment approach if
that is the established safeguard.

In `@README.md`:
- Line 3: Update the product description sentence in README.md to use the plural
form “RPM-based Linux distributions” so it agrees with “package sets.”

---

Nitpick comments:
In `@content_resolver/config_manager.py`:
- Around line 864-866: Update the YAML error handler around the ConfigError
raise to explicitly chain the caught yaml.YAMLError as the cause using the
existing err variable, while preserving the current message and yml_file
context.

In `@Dockerfile`:
- Around line 3-4: Restore the minimal-install controls on the DNF update and
install commands in the Dockerfile by reapplying tsflags=nodocs and
install_weak_deps=False. Keep the existing package installation unchanged and
verify the resulting image size and package list remain minimal.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd96de62-ed8a-4b42-8653-3d3cfab0b926

📥 Commits

Reviewing files that changed from the base of the PR and between c572ab5 and 3621465.

📒 Files selected for processing (9)
  • .github/workflows/docker-image.yml
  • .github/workflows/fork-only-workflow.yml
  • Dockerfile
  • README.md
  • content_resolver/analyzer.py
  • content_resolver/config_manager.py
  • content_resolver/historia_data.py
  • content_resolver/page_generation.py
  • content_resolver/utils.py

Comment on lines +123 to +127
except KeyError:
# FIXME: No reference for `yaml_file`
raise ConfigError("'{file}.yaml' - is invalid. Repo {id} doesn't list baseurl.".format(
file=yml_file,
id=id))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)content_resolver/config_manager\.py$' || true

echo "== context around lines 100-140 =="
if [ -f content_resolver/config_manager.py ]; then
  sed -n '80,140p' content_resolver/config_manager.py | nl -ba -v80
fi

echo "== search yml_file/document_id/baseurl in file =="
rg -n "yml_file|document_id|baseurl|_load_config_repo_v2|baseurl" content_resolver/config_manager.py

echo "== outline nearby symbols =="
ast-grep outline content_resolver/config_manager.py --match _load_config_repo_v2 --view expanded || true

Repository: fedora-eln/content-resolver

Length of output: 293


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== context around lines 100-140 =="
sed -n '80,140p' content_resolver/config_manager.py | cat -n

echo "== search yml_file/document_id/baseurl in file =="
rg -n "yml_file|document_id|baseurl|_load_config_repo_v2|baseurl" content_resolver/config_manager.py || true

echo "== outline nearby symbols =="
ast-grep outline content_resolver/config_manager.py --match _load_config_repo_v2 --view expanded || true

Repository: fedora-eln/content-resolver

Length of output: 9215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

path = Path("content_resolver/config_manager.py")
tree = ast.parse(path.read_text())

func = None
for node in ast.walk(tree):
    if isinstance(node, ast.FunctionDef) and node.name == "_load_config_repo_v2":
        func = node
        break

if func is None:
    raise SystemExit("function not found")

print("function_start_line", func.lineno)
scope_names = {target.arg for target in func.args.args}
print("function_args", scope_names)

for node in ast.walk(func):
    if isinstance(node, ast.Name) and node.id == "yml_file":
        print("yml_file_ref", node.lineno, "context:", ast.unparse(node.slice).replace("'", "'")) if isinstance(node.ctx, ast.Subscript) else print("yml_file_ref", node.lineno)
        print("yml_file_context", ast.unparse(node).upper())
PY

Repository: fedora-eln/content-resolver

Length of output: 293


Fix the missing baseurl field reference with the available filename.

_load_config_repo_v2 receives document_id, but the KeyError handler formats yml_file, which is not defined. If repo_data["baseurl"] is missing, this raises NameError before returning a load error.

Proposed fix
-            except KeyError:
+            except KeyError as err:
                 # FIXME:  No reference for `yaml_file`
-                raise ConfigError("'{file}.yaml' - is invalid. Repo {id} doesn't list baseurl.".format(
-                    file=yml_file,
-                    id=id))
+                raise ConfigError("'{file}.yaml' - is invalid. Repo {id} doesn't list baseurl.".format(
+                    file=document_id,
+                    id=id)) from err
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except KeyError:
# FIXME: No reference for `yaml_file`
raise ConfigError("'{file}.yaml' - is invalid. Repo {id} doesn't list baseurl.".format(
file=yml_file,
id=id))
except KeyError as err:
# FIXME: No reference for `yaml_file`
raise ConfigError("'{file}.yaml' - is invalid. Repo {id} doesn't list baseurl.".format(
file=document_id,
id=id)) from err
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 125-127: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[error] 126-126: Undefined name yml_file

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content_resolver/config_manager.py` around lines 123 - 127, Update the
KeyError handler in _load_config_repo_v2 to format ConfigError with the
available document_id value instead of the undefined yml_file, preserving the
existing missing-baseurl error message behavior.

Source: Linters/SAST tools

Comment on lines +973 to +976
except:
raise ConfigError("Error loading a JSON data file '{filename}': {err}".format(
filename=json_file,
err=err))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)content_resolver/config_manager\.py$|config_manager\.py$' || true

echo
echo "Relevant source around lines 950-985:"
if [ -f content_resolver/config_manager.py ]; then
  sed -n '940,985p' content_resolver/config_manager.py | nl -ba -v940
fi

echo
echo "Exception handlers with err reference in file:"
rg -n "except|err|ConfigError|load_data|json_file|%s|\\{err\\}" content_resolver/config_manager.py || true

echo
echo "Python semantic probe for bare except and undefined variable in except handler:"
python3 - <<'PY'
def load_raises():
    raise ValueError("original failure")

try:
    try:
        load_raises()
    except:
        raise Exception("wrapper failed: {err}".format(err=err))
except Exception as e:
    print("caught:", type(e).__name__)
    print("args:", args := e.args)
    print("cause:", type(e.__cause__).__name__ if e.__cause__ is not None else None)
PY

Repository: fedora-eln/content-resolver

Length of output: 287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)content_resolver/config_manager\.py$|config_manager\.py$' || true

echo
echo "Relevant source around lines 950-985:"
if [ -f content_resolver/config_manager.py ]; then
  sed -n '940,985p' content_resolver/config_manager.py | nl -ba -v940
fi

echo
echo "Exception handlers with err reference in file:"
rg -n "except|err|ConfigError|load_data|json_file|%s|\\{err\\}" content_resolver/config_manager.py || true

echo
	echo "Python semantic probe for bare except and undefined variable in except handler:"
python3 - <<'PY'
def load_raises():
    raise ValueError("original failure")

try:
    try:
        load_raises()
    except:
        raise Exception("wrapper failed: {err}".format(err=err))
except Exception as e:
    print("caught:", type(e).__name__)
    print("args:", args := e.args)
    print("cause:", type(e.__cause__).__name__ if e.__cause__ is not None else None)
PY

Repository: fedora-eln/content-resolver

Length of output: 287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path("content_resolver/config_manager.py")
lines = path.read_text(encoding="utf-8").splitlines()
for start, end in [(940, 990), (1, 50)]:
    print(f"\nLines {start}-{end}:")
    for i in range(max(1, start), min(len(lines), end) + 1):
        print(f"{i:5}: {lines[i-1]}")
PY

echo
echo "Relevant exception handling references:"
python3 - <<'PY'
from pathlib import Path
for i, line in enumerate(Path("content_resolver/config_manager.py").read_text().splitlines(), 1):
    if any(tok in line for tok in ["except", "ConfigError", "load_data", "json_file", "{err}", "parse_files", "parse_file"]):
        print(f"{i:5}: {line}")
PY

echo
echo "Semantic probe for bare except and undefined except-bound variable:"
python3 - <<'PY'
def load_data():
    raise ValueError("original failure")

try:
    try:
        load_data()
    except:
        raise BaseException("wrapper failed: {err}".format(err=err))
except BaseException as exc:
    print("caught:", type(exc).__name__)
    print("exception raises NameError:", type(exc).__name__ == "NameError")
PY

Repository: fedora-eln/content-resolver

Length of output: 9163


Bind the JSON load exception and avoid a bare except.

If self.load_data(...) fails, except: catches the failure but does not assign err. Formatting the ConfigError message then raises NameError, so the bad JSON file is not added to the reported errors. Catch the specific exceptions from load_data; if this wrapper must cover ordinary failures, use except Exception as err and chain the exception.

🧰 Tools
🪛 Ruff (0.16.1)

[error] 973-973: Do not use bare except

(E722)


[warning] 974-976: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[error] 976-976: Undefined name err

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content_resolver/config_manager.py` around lines 973 - 976, Update the
exception handler around self.load_data(...) to bind the caught error and
replace the bare except with the appropriate specific exceptions, or except
Exception as err if ordinary failures must be covered. Preserve the ConfigError
message while chaining the original exception so the JSON file failure is
reported without raising NameError.

Source: Linters/SAST tools

Comment on lines 132 to +135
return historic_data

log(" Done!")
log("")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the completion log before the return.

return historic_data on Line 132 exits _read_historic_data. The new log calls on Lines 134-135 never execute. Move both calls before the return.

Proposed fix
-    return historic_data
-
     log("  Done!")
     log("")
+    return historic_data
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return historic_data
log(" Done!")
log("")
log(" Done!")
log("")
return historic_data
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content_resolver/historia_data.py` around lines 132 - 135, In
_read_historic_data, move the “Done!” and blank-line log calls before return
historic_data so both messages execute while preserving the returned
historic_data value.

Comment on lines +27 to +29
filename = ("{page_name}.html".format(
page_name=page_name.replace(":", "--")
))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Preserve the output-directory boundary.

The new filename keeps path separators from page_name. The previous basename containment was removed. Callers insert metadata into page names, including maintainer, pkg_name, and srpm_name on Lines 274-282 and 478-498. If a value contains / and traversal components, open(os.path.join(output, filename), "w") on Line 34 can write outside output. A slash without traversal can also cause a failure because nested directories are not created. Reject path separators and verify the resolved path stays under output, or restore the basename check.

Proposed fix
-    filename = ("{page_name}.html".format(
-        page_name=page_name.replace(":", "--")
-    ))
+    safe_page_name = page_name.replace(":", "--")
+    if "/" in safe_page_name or "\\" in safe_page_name:
+        raise ValueError("Invalid page name")
+    filename = "{}.html".format(safe_page_name)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
filename = ("{page_name}.html".format(
page_name=page_name.replace(":", "--")
))
safe_page_name = page_name.replace(":", "--")
if "/" in safe_page_name or "\\" in safe_page_name:
raise ValueError("Invalid page name")
filename = "{}.html".format(safe_page_name)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content_resolver/page_generation.py` around lines 27 - 29, Update filename
construction in the page-generation flow to reject page_name values containing
path separators or traversal components, and verify the resolved output path
remains within output before writing. Preserve valid filename generation while
preventing nested-path failures and writes outside the output directory; use the
existing basename containment approach if that is the established safeguard.

Source: Linters/SAST tools

Comment thread README.md
[![Content Resolver CI](https://github.com/fedora-eln/content-resolver/actions/workflows/docker-image.yml/badge.svg)](https://github.com/fedora-eln/content-resolver/actions/workflows/docker-image.yml)

Content Resolver makes it easy to define and inspect package sets of RPM-based Linux distributions.
Content Resolver makes it easy to define and inspect package sets of RPM-based Linux distribution.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the product description grammar.

Line 3 uses “package sets of RPM-based Linux distribution” without an article. Use the plural form for consistency with “package sets.”

Proposed wording
-Content Resolver makes it easy to define and inspect package sets of RPM-based Linux distribution.
+Content Resolver makes it easy to define and inspect package sets for RPM-based Linux distributions.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Content Resolver makes it easy to define and inspect package sets of RPM-based Linux distribution.
Content Resolver makes it easy to define and inspect package sets for RPM-based Linux distributions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 3, Update the product description sentence in README.md to
use the plural form “RPM-based Linux distributions” so it agrees with “package
sets.”

@bhoy-troy bhoy-troy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good

@yselkowitz
yselkowitz merged commit fb79403 into master Aug 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants