Skip to content

Include store disk usage stats in info output#2319

Open
olliewalsh wants to merge 1 commit into
containers:mainfrom
olliewalsh:konflux_tmp
Open

Include store disk usage stats in info output#2319
olliewalsh wants to merge 1 commit into
containers:mainfrom
olliewalsh:konflux_tmp

Conversation

@olliewalsh
Copy link
Copy Markdown
Collaborator

@olliewalsh olliewalsh commented Jan 15, 2026

Summary by Sourcery

Add reporting of disk usage statistics for the store path to the CLI info output.

New Features:

  • Expose store disk usage statistics (total, used, free) in the info CLI command output.

Tests:

  • Add unit tests covering store disk usage retrieval, including non-existent store paths and missing root directory scenarios.

@sourcery-ai

This comment was marked as outdated.

@gemini-code-assist

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

@olliewalsh olliewalsh force-pushed the konflux_tmp branch 2 times, most recently from dbd8dd4 to a5c4d5c Compare January 16, 2026 14:03
@rhatdan

This comment was marked as resolved.

@rhatdan
Copy link
Copy Markdown
Member

rhatdan commented Feb 5, 2026

Good idea on this feature.

sourcery-ai[bot]

This comment was marked as outdated.

Comment thread ramalama/common.py Outdated
@rhatdan

This comment was marked as resolved.

@olliewalsh
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • The info command now includes a StoreUsage field reporting disk total, used, and free for the configured store. It probes parent directories if the store path is missing and returns null values if no existing path is found.
  • Tests

    • Added unit tests covering successful disk stats, retries when initial paths are missing, permission-error retry behavior, and the all-fail (null) case.

Walkthrough

Added a function to compute disk usage for a store path with upward traversal on errors, exposed it in the CLI info output as a new JSON field "StoreUsage", and added unit tests covering success and various error/walk behaviors.

Changes

Cohort / File(s) Summary
Store utilities
ramalama/common.py
Added `store_disk_stats(Path) -> dict[str, Optional[int]]
CLI
ramalama/cli.py
Imported common and extended info subcommand JSON output with a new "StoreUsage" field populated from common.store_disk_stats(Path(args.store)).
Tests
test/unit/test_common.py
Added TestStoreDiskStats with cases: successful disk_usage, initial FileNotFoundError then success (walk-up), all attempts FileNotFoundError (returns Nones), and PermissionError then success.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as "ramalama.cli"
    participant Common as "ramalama.common"
    participant OS as "shutil / filesystem"

    User->>CLI: run `ramalama info --store <path>`
    CLI->>Common: store_disk_stats(Path(<path>))
    Common->>OS: shutil.disk_usage(<path>)
    alt disk_usage succeeds
        OS-->>Common: (total, used, free)
    else disk_usage raises OSError
        Common->>OS: shutil.disk_usage(parent path) (repeat until root)
        alt eventually succeeds
            OS-->>Common: (total, used, free)
        else reaches root without success
            OS-->>Common: raises OSError / no usable path
            Common-->>Common: return {"total": null, "used": null, "free": null}
        end
    end
    Common-->>CLI: dict with disk stats or nulls
    CLI-->>User: JSON output includes "StoreUsage"
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibble bytes and count the store,
Up through parents, I hop and explore.
If disks are found, I share their cheer,
If none appear, I whisper "none here".
— a rabbit with a magnifying paw 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Include store disk usage stats in info output' clearly and concisely summarizes the main change: adding disk usage statistics to the info command output.
Description check ✅ Passed The description is directly related to the changeset, explaining the new feature of exposing store disk usage statistics and mentioning the corresponding tests added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/unit/test_common.py (1)

525-532: Consider asserting the traversal call sequence explicitly.

These tests validate outputs, but they don’t assert that Line 526-532 actually walked /tmp/store -> /tmp -> /. Adding assert_has_calls(...) would tighten regression detection for parent-walk logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/test_common.py` around lines 525 - 532, Add explicit assertions
that the mocked disk usage function was called in the expected parent-traversal
order: after calling store_disk_stats in tests
test_store_disk_stats_no_store_dir and test_store_disk_stats_root_not_found,
assert mock_disk_usage.assert_has_calls([call("/tmp/store"), call("/tmp"),
call("/")]) (using unittest.mock.call) so the tests verify the walk sequence
when store_disk_stats (the function under test) attempts disk_usage on the store
path, its parent, and root.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ramalama/common.py`:
- Around line 793-799: The disk usage loop currently only catches
FileNotFoundError which misses other OSError subclasses (e.g., PermissionError);
update the exception handler around shutil.disk_usage(current_path) to catch
OSError instead of FileNotFoundError and keep the fallback/parent-walking logic,
and simplify the return by unpacking the tuple from shutil.disk_usage into
total, used, free and returning {"total": total, "used": used, "free": free}
rather than using dict(zip(...)); reference the shutil.disk_usage call and
current_path in the loop to locate the change.

---

Nitpick comments:
In `@test/unit/test_common.py`:
- Around line 525-532: Add explicit assertions that the mocked disk usage
function was called in the expected parent-traversal order: after calling
store_disk_stats in tests test_store_disk_stats_no_store_dir and
test_store_disk_stats_root_not_found, assert
mock_disk_usage.assert_has_calls([call("/tmp/store"), call("/tmp"), call("/")])
(using unittest.mock.call) so the tests verify the walk sequence when
store_disk_stats (the function under test) attempts disk_usage on the store
path, its parent, and root.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a772f485-47f8-4a3a-91c9-93ac9035a4e6

📥 Commits

Reviewing files that changed from the base of the PR and between 25f7f25 and 414ab84.

📒 Files selected for processing (3)
  • ramalama/cli.py
  • ramalama/common.py
  • test/unit/test_common.py

Comment thread ramalama/common.py Outdated
@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
ramalama/common.py (1)

805-825: ⚠️ Potential issue | 🟠 Major

Catch broader OS errors in store_disk_stats to prevent info crashes.

At Line 821, only FileNotFoundError is handled; shutil.disk_usage() can fail with other OSError subclasses (for example PermissionError). Also, Line 820/Line 824 can avoid zip(...) entirely.

🔧 Proposed fix
-def store_disk_stats(store_path: Path) -> dict[str, Optional[int]] | None:
+def store_disk_stats(store_path: Path) -> dict[str, Optional[int]]:
@@
     current_path = store_path
     while True:
         try:
-            return dict(zip(("total", "used", "free"), shutil.disk_usage(current_path)))
-        except FileNotFoundError:
+            total, used, free = shutil.disk_usage(current_path)
+            return {"total": total, "used": used, "free": free}
+        except OSError:
             if current_path == current_path.parent:
                 # Reached the root of the filesystem.
-                return dict(zip(("total", "used", "free"), (None, None, None)))
+                return {"total": None, "used": None, "free": None}
             current_path = current_path.parent
#!/bin/bash
# Verify current exception handling + test coverage for store_disk_stats.
rg -n -C3 'def store_disk_stats|shutil\.disk_usage|except FileNotFoundError|except OSError|TestStoreDiskStats|PermissionError' \
  ramalama/common.py test/unit/test_common.py

# Verify exception hierarchy assumption used by the recommendation.
python - <<'PY'
print(issubclass(PermissionError, OSError))
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ramalama/common.py` around lines 805 - 825, The function store_disk_stats
currently only catches FileNotFoundError when calling
shutil.disk_usage(current_path); broaden the except to OSError to also cover
PermissionError and similar OS-level failures, and simplify the return so you
construct the dict directly (e.g., assign total, used, free =
shutil.disk_usage(current_path) and return {"total": total, "used": used,
"free": free} or return {"total": None, "used": None, "free": None} when at
filesystem root) while preserving the upward traversal using current_path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ramalama/common.py`:
- Around line 805-825: The function store_disk_stats currently only catches
FileNotFoundError when calling shutil.disk_usage(current_path); broaden the
except to OSError to also cover PermissionError and similar OS-level failures,
and simplify the return so you construct the dict directly (e.g., assign total,
used, free = shutil.disk_usage(current_path) and return {"total": total, "used":
used, "free": free} or return {"total": None, "used": None, "free": None} when
at filesystem root) while preserving the upward traversal using current_path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9b4554f3-c390-41ee-9dc4-443f8fb867bb

📥 Commits

Reviewing files that changed from the base of the PR and between 4501930 and b9f6ef6.

📒 Files selected for processing (3)
  • ramalama/cli.py
  • ramalama/common.py
  • test/unit/test_common.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • ramalama/cli.py

@coderabbitai

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

Signed-off-by: Oliver Walsh <owalsh@redhat.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
ramalama/common.py (1)

805-826: Tighten the return type to match actual behavior.

store_disk_stats never returns None; it always returns a dict (with nullable values on failure). Keeping | None in the signature makes callers handle an impossible case.

Proposed type-signature fix
-def store_disk_stats(store_path: Path) -> dict[str, Optional[int]] | None:
+def store_disk_stats(store_path: Path) -> dict[str, int | None]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ramalama/common.py` around lines 805 - 826, The function store_disk_stats
always returns a dict with possibly-None values rather than None itself, so
tighten the type signature by removing the `| None` union and use `dict[str,
Optional[int]]` as the return type; update the function signature for
store_disk_stats and adjust its docstring if necessary to state it returns a
dict with nullable values on failure so callers no longer need to handle a None
return.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/unit/test_common.py`:
- Around line 691-710: Tests in TestStoreDiskStats are using a hardcoded temp
path Path("/tmp/store") which triggers Ruff S108; replace those occurrences with
a synthetic non-filesystem path constant (e.g., TEST_STORE_PATH or a
Path("fake_store") defined in the test module) to avoid using real /tmp. Update
the four test methods (test_store_disk_stats,
test_store_disk_stats_no_store_dir, test_store_disk_stats_root_not_found,
test_store_disk_stats_permission_error) to reference the new constant instead of
Path("/tmp/store") and ensure imports/reference use pathlib.Path where needed so
assertions against store_disk_stats(Path(...)) remain unchanged.

---

Nitpick comments:
In `@ramalama/common.py`:
- Around line 805-826: The function store_disk_stats always returns a dict with
possibly-None values rather than None itself, so tighten the type signature by
removing the `| None` union and use `dict[str, Optional[int]]` as the return
type; update the function signature for store_disk_stats and adjust its
docstring if necessary to state it returns a dict with nullable values on
failure so callers no longer need to handle a None return.
🪄 Autofix (Beta)

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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b549344c-942e-4feb-af25-e90d9c213a9e

📥 Commits

Reviewing files that changed from the base of the PR and between b9f6ef6 and 48cec66.

📒 Files selected for processing (3)
  • ramalama/cli.py
  • ramalama/common.py
  • test/unit/test_common.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • ramalama/cli.py

Comment thread test/unit/test_common.py
Comment on lines +691 to +710
class TestStoreDiskStats:
@patch("ramalama.common.shutil.disk_usage")
def test_store_disk_stats(self, mock_disk_usage):
mock_disk_usage.return_value = (1000, 200, 800)
assert store_disk_stats(Path("/tmp/store")) == {"total": 1000, "used": 200, "free": 800}

@patch("ramalama.common.shutil.disk_usage")
def test_store_disk_stats_no_store_dir(self, mock_disk_usage):
mock_disk_usage.side_effect = [FileNotFoundError, FileNotFoundError, (1000, 200, 800)]
assert store_disk_stats(Path("/tmp/store")) == {"total": 1000, "used": 200, "free": 800}

@patch("ramalama.common.shutil.disk_usage")
def test_store_disk_stats_root_not_found(self, mock_disk_usage):
mock_disk_usage.side_effect = [FileNotFoundError, FileNotFoundError, FileNotFoundError]
assert store_disk_stats(Path("/tmp/store")) == {"total": None, "used": None, "free": None}

@patch("ramalama.common.shutil.disk_usage")
def test_store_disk_stats_permission_error(self, mock_disk_usage):
mock_disk_usage.side_effect = [PermissionError, (1000, 200, 800)]
assert store_disk_stats(Path("/tmp/store")) == {"total": 1000, "used": 200, "free": 800}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace hardcoded /tmp test path to unblock Ruff S108.

Lines 695, 700, 705, and 710 use Path("/tmp/store"), which triggers S108 and can block CI. Use a non-temp synthetic path constant instead.

Proposed fix
 class TestStoreDiskStats:
+    TEST_STORE_PATH = Path("/ramalama/store")
+
     `@patch`("ramalama.common.shutil.disk_usage")
     def test_store_disk_stats(self, mock_disk_usage):
         mock_disk_usage.return_value = (1000, 200, 800)
-        assert store_disk_stats(Path("/tmp/store")) == {"total": 1000, "used": 200, "free": 800}
+        assert store_disk_stats(self.TEST_STORE_PATH) == {"total": 1000, "used": 200, "free": 800}
 
     `@patch`("ramalama.common.shutil.disk_usage")
     def test_store_disk_stats_no_store_dir(self, mock_disk_usage):
         mock_disk_usage.side_effect = [FileNotFoundError, FileNotFoundError, (1000, 200, 800)]
-        assert store_disk_stats(Path("/tmp/store")) == {"total": 1000, "used": 200, "free": 800}
+        assert store_disk_stats(self.TEST_STORE_PATH) == {"total": 1000, "used": 200, "free": 800}
 
     `@patch`("ramalama.common.shutil.disk_usage")
     def test_store_disk_stats_root_not_found(self, mock_disk_usage):
         mock_disk_usage.side_effect = [FileNotFoundError, FileNotFoundError, FileNotFoundError]
-        assert store_disk_stats(Path("/tmp/store")) == {"total": None, "used": None, "free": None}
+        assert store_disk_stats(self.TEST_STORE_PATH) == {"total": None, "used": None, "free": None}
 
     `@patch`("ramalama.common.shutil.disk_usage")
     def test_store_disk_stats_permission_error(self, mock_disk_usage):
         mock_disk_usage.side_effect = [PermissionError, (1000, 200, 800)]
-        assert store_disk_stats(Path("/tmp/store")) == {"total": 1000, "used": 200, "free": 800}
+        assert store_disk_stats(self.TEST_STORE_PATH) == {"total": 1000, "used": 200, "free": 800}
🧰 Tools
🪛 Ruff (0.15.9)

[error] 695-695: Probable insecure usage of temporary file or directory: "/tmp/store"

(S108)


[error] 700-700: Probable insecure usage of temporary file or directory: "/tmp/store"

(S108)


[error] 705-705: Probable insecure usage of temporary file or directory: "/tmp/store"

(S108)


[error] 710-710: Probable insecure usage of temporary file or directory: "/tmp/store"

(S108)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/test_common.py` around lines 691 - 710, Tests in TestStoreDiskStats
are using a hardcoded temp path Path("/tmp/store") which triggers Ruff S108;
replace those occurrences with a synthetic non-filesystem path constant (e.g.,
TEST_STORE_PATH or a Path("fake_store") defined in the test module) to avoid
using real /tmp. Update the four test methods (test_store_disk_stats,
test_store_disk_stats_no_store_dir, test_store_disk_stats_root_not_found,
test_store_disk_stats_permission_error) to reference the new constant instead of
Path("/tmp/store") and ensure imports/reference use pathlib.Path where needed so
assertions against store_disk_stats(Path(...)) remain unchanged.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it's a unit-test, not an issue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@olliewalsh, you're right — since shutil.disk_usage is fully mocked, the path Path("/tmp/store") is never accessed on the real filesystem, making this a false positive for S108. I'll disregard this flag in unit test contexts going forward.


✏️ Learnings added
Learnt from: olliewalsh
Repo: containers/ramalama PR: 2319
File: test/unit/test_common.py:691-710
Timestamp: 2026-04-10T13:20:26.718Z
Learning: In `test/unit/test_common.py`, Ruff S108 warnings on `/tmp/store` paths inside `TestStoreDiskStats` are false positives. The path is only passed to a fully mocked `shutil.disk_usage` and never touches the real filesystem, so S108 does not apply in this unit test context.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@olliewalsh olliewalsh enabled auto-merge April 10, 2026 13:20
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.

3 participants