Skip to content

Conversation

saifulmuhajir
Copy link

Related to #139

Copy link

coderabbitai bot commented Sep 23, 2025

Walkthrough

Adds CNPGCluster support to the Kubernetes integration (discovery via CustomObjects API, CNPG-aware pod selection and allocation extraction), extends KindLiteral, updates CLI help text, switches formatters to safe dict lookups, and adds RBAC rules for CNPG clusters.

Changes

Cohort / File(s) Summary
Kubernetes integration: CNPG support
robusta_krr/core/integrations/kubernetes/__init__.py
Adds _list_cnpg_clusters and integrates CNPGCluster into list_scannable_objects, adds CNPG branch in list_pods, updates __build_scannable_object to extract allocations from item.spec.resources and set container_name="postgres", and treats missing CNPG CRD like other CRD-not-available cases.
Model kind expansion
robusta_krr/core/models/objects.py
Extends KindLiteral to include "CNPGCluster".
Formatters: safe resource lookups
robusta_krr/formatters/csv.py, robusta_krr/formatters/csv_raw.py, robusta_krr/formatters/table.py
Replace direct dict indexing with safe .get(..., None) calls when reading allocations/recommendations and add a guard in CSV total diff to return empty if recommended is None.
CLI help text
robusta_krr/main.py
Updates --resource help string to include CNPGCluster.
RBAC for CNPG
tests/single_namespace_permissions.yaml
Adds Role rule granting get and list on clusters in API group postgresql.cnpg.io.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant ClusterLoader
  participant K8sCore as K8s Core APIs
  participant COAPI as CustomObjects API

  User->>ClusterLoader: list_scannable_objects()
  ClusterLoader->>K8sCore: List Deployments/StatefulSets/...
  ClusterLoader->>COAPI: List CNPGCluster (cluster & namespaced)
  Note over ClusterLoader: Build K8sObjectData for each item\n(CNPG: allocations from item.spec.resources,\ncontainer_name="postgres")
  ClusterLoader-->>User: [K8sObjectData...]
Loading
sequenceDiagram
  autonumber
  participant Consumer
  participant ClusterLoader
  participant K8sCore as K8s Core APIs

  Consumer->>ClusterLoader: list_pods(object)
  alt object.kind == "CNPGCluster"
    ClusterLoader->>K8sCore: List Pods selector: cnpg.io/cluster={name}
  else
    ClusterLoader->>K8sCore: List Pods via standard selectors
  end
  ClusterLoader-->>Consumer: [PodData...]
Loading
sequenceDiagram
  autonumber
  participant Builder as __build_scannable_object
  participant Item as K8s Item

  Item-->>Builder: item, container, kind
  alt kind == "CNPGCluster"
    Note right of Builder: allocations <- item.spec.resources\ncontainer_name = "postgres"
  else
    Note right of Builder: allocations <- container.resources\ncontainer_name = container.name
  end
  Builder-->>Item: K8sObjectData
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The description "Related to #139" is on-topic but overly terse and does not summarize the actual changes made (CNPGCluster support, type update, new listing logic, formatter guards, and RBAC addition), so it is too generic to be fully informative. Please expand the PR description to briefly list the key changes (e.g., added CNPGCluster KindLiteral, _list_cnpg_clusters implementation, pod selector and allocation extraction changes, formatter safeguards, and added RBAC rule) and note any migration or testing considerations so reviewers can quickly understand the scope.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Support cloudnative-pg (CNPG) clusters" is concise and accurately reflects the primary change in the PR — adding CNPGCluster support across the Kubernetes integration (KindLiteral update, new _list_cnpg_clusters, selector/pod handling, and RBAC update) — and is clear and specific enough for a reviewer scanning history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b79d7fe and d955dd9.

📒 Files selected for processing (2)
  • robusta_krr/core/integrations/kubernetes/__init__.py (8 hunks)
  • robusta_krr/formatters/csv.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • robusta_krr/formatters/csv.py
🧰 Additional context used
🧬 Code graph analysis (1)
robusta_krr/core/integrations/kubernetes/__init__.py (3)
robusta_krr/core/models/allocations.py (3)
  • ResourceType (13-20)
  • ResourceAllocations (52-106)
  • from_container (79-106)
robusta_krr/core/models/objects.py (2)
  • selector (76-83)
  • K8sObjectData (38-107)
robusta_krr/utils/object_like_dict.py (2)
  • get (25-26)
  • ObjectLikeDict (1-29)
🔇 Additional comments (7)
robusta_krr/core/integrations/kubernetes/__init__.py (7)

101-112: Good: CNPG clusters included in discovery.

Adding _list_cnpg_clusters into the gather keeps the discovery flow uniform.


219-235: Resolved: CNPG allocations now keyed by ResourceType.

This addresses the earlier issue where string keys prevented mapping. Container name "postgres" is reasonable.

Double-check that spec.resources.{requests,limits} exist at spec.resources (and not under another nesting for certain CNPG versions).


258-260: LGTM: Properly set container and allocations on K8sObjectData.

Keeps downstream formatters stable.


340-344: Good: Treat CNPG API-not-available like other CRDs.

Prevents noisy logs on clusters without the CRD.


350-372: CNPG listing implementation looks correct.

Group/version/plural match CNPG; passing the item itself for allocation extraction is appropriate.

Ensure RBAC includes list on postgresql.cnpg.io clusters (both namespaced and cluster-scoped where applicable).


23-25: No change required — result.py re-exports ResourceType and ResourceAllocations. result.py imports these from robusta_krr.core.models.allocations, so the current imports from robusta_krr.core.models.result are valid.


151-153: CNPG pod selector label confirmed

CloudNativePG pods use the label key cnpg.io/cluster with the value set to the Cluster name, so selector = f"cnpg.io/cluster={object.name}" is correct.


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 and usage tips.

Copy link

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
robusta_krr/formatters/table.py (2)

20-26: Fix None-deref when recommendations are missing.

recommended can now be None; accessing recommended.severity/recommended.value will raise. Short-circuit and return NONE_LITERAL.

Apply this diff:

 def _format_request_str(item: ResourceScan, resource: ResourceType, selector: str) -> str:
-    allocated = getattr(item.object.allocations, selector).get(resource, None)
+    allocated = getattr(item.object.allocations, selector).get(resource, None)
     info = item.recommended.info.get(resource)
-    recommended = getattr(item.recommended, selector).get(resource, None)
-    severity = recommended.severity
+    recommended = getattr(item.recommended, selector).get(resource, None)
+    if recommended is None:
+        return f"{NONE_LITERAL}"
+    severity = recommended.severity

50-61: Guard format_diff call when recommendation is absent.

Avoid passing None into format_diff.

Apply this diff:

 def _format_total_diff(item: ResourceScan, resource: ResourceType, pods_current: int) -> str:
     selector = "requests"
-    allocated = getattr(item.object.allocations, selector).get(resource, None)
-    recommended = getattr(item.recommended, selector).get(resource, None)
+    allocated = getattr(item.object.allocations, selector).get(resource, None)
+    recommended = getattr(item.recommended, selector).get(resource, None)
+    if recommended is None:
+        return ""
robusta_krr/formatters/csv.py (1)

29-41: Prevent AttributeError when recommendation is missing.

recommended may be None now; recommended.value access will crash. Short-circuit.

Apply this diff:

 def _format_request_str(item: ResourceScan, resource: ResourceType, selector: str) -> str:
-    allocated = getattr(item.object.allocations, selector).get(resource, None)
-    recommended = getattr(item.recommended, selector).get(resource, None)
+    allocated = getattr(item.object.allocations, selector).get(resource, None)
+    recommended = getattr(item.recommended, selector).get(resource, None)
+    if recommended is None:
+        return f"{NONE_LITERAL}"
 
-    if allocated is None and recommended.value is None:
+    if allocated is None and recommended.value is None:
         return f"{NONE_LITERAL}"
 
     diff = format_diff(allocated, recommended, selector)
     if diff != "":
         diff = f"({diff}) "
 
     return diff + format_recommendation_value(allocated) + " -> " + format_recommendation_value(recommended.value)
🧹 Nitpick comments (1)
robusta_krr/core/integrations/kubernetes/__init__.py (1)

443-466: Remove unused and incorrect CNPG pods helper.

This method isn’t referenced, uses kind="Cluster" (not in KindLiteral), and an extract_containers shape that doesn’t apply to CNPG. Prefer deleting to avoid confusion.

Apply this diff to remove it:

-    def _list_cnpgpods(self) -> list[K8sObjectData]:
-        # NOTE: Using custom objects API returns dicts, but all other APIs return objects
-        # We need to handle this difference using a small wrapper
-        return self._list_scannable_objects(
-            kind="Cluster",
-            all_namespaces_request=lambda **kwargs: ObjectLikeDict(
-                self.custom_objects.list_cluster_custom_object(
-                    group="postgresql.cnpg.io",
-                    version="v1",
-                    plural="clusters",
-                    **kwargs,
-                )
-            ),
-            namespaced_request=lambda **kwargs: ObjectLikeDict(
-                self.custom_objects.list_namespaced_custom_object(
-                    group="postgresql.cnpg.io",
-                    version="v1",
-                    plural="clusters",
-                    **kwargs,
-                )
-            ),
-            extract_containers=lambda item: item.spec.pods[0].spec.containers,
-        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baaffc6 and a07d54d.

📒 Files selected for processing (7)
  • robusta_krr/core/integrations/kubernetes/__init__.py (7 hunks)
  • robusta_krr/core/models/objects.py (1 hunks)
  • robusta_krr/formatters/csv.py (2 hunks)
  • robusta_krr/formatters/csv_raw.py (1 hunks)
  • robusta_krr/formatters/table.py (2 hunks)
  • robusta_krr/main.py (1 hunks)
  • tests/single_namespace_permissions.yaml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
robusta_krr/formatters/csv_raw.py (2)
robusta_krr/core/models/result.py (1)
  • ResourceScan (25-54)
robusta_krr/core/models/allocations.py (1)
  • ResourceType (13-20)
robusta_krr/core/integrations/kubernetes/__init__.py (3)
robusta_krr/core/models/objects.py (2)
  • selector (76-83)
  • K8sObjectData (38-107)
robusta_krr/utils/object_like_dict.py (2)
  • get (25-26)
  • ObjectLikeDict (1-29)
robusta_krr/core/models/allocations.py (2)
  • ResourceAllocations (52-106)
  • from_container (79-106)
🔇 Additional comments (9)
tests/single_namespace_permissions.yaml (1)

29-31: RBAC addition for CNPG clusters looks good.

Correct API group and plural. Matches how CNPG clusters are discovered in code.

robusta_krr/main.py (1)

101-103: Help text update LGTM.

Including CNPGCluster in --resource help keeps CLI aligned with new kind.

robusta_krr/formatters/csv_raw.py (2)

44-47: Safer access: good change.

Using .get(...) avoids KeyError and aligns with other formatters.


51-55: Safer access: good change.

Consistent with the project-wide guarded lookups.

robusta_krr/core/models/objects.py (1)

11-11: CNPGCluster kind addition LGTM.

Extending KindLiteral to include CNPGCluster matches the integration changes.

robusta_krr/core/integrations/kubernetes/__init__.py (4)

150-152: Confirm CNPG pod selector label.

Selector cnpg.io/cluster= is assumed. Please confirm this label is stable across your supported CNPG versions.

If needed, I can adjust to a configurable label.


331-335: Graceful handling for CNPG API absence looks good.

CNPGCluster added to the “API not available” skip list.


341-363: Name/type correctness of CNPG listing helper.

new helper is good, but ensure import of ResourceType (suggested above) to fix allocations mapping. No other issues with using CustomObjectsApi + ObjectLikeDict.

Would you like me to add a minimal e2e test that stubs a CNPG cluster object and validates parsed allocations?


23-25: ResourceAllocations import is valid — no change needed. result.py imports ResourceAllocations from core.models.allocations (from robusta_krr.core.models.allocations import RecommendationValue, ResourceAllocations, ResourceType), so from robusta_krr.core.models.result import ResourceAllocations will succeed at runtime.

Copy link

@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: 0

🧹 Nitpick comments (1)
robusta_krr/core/integrations/kubernetes/__init__.py (1)

193-199: CRD selector: matchExpressions branch iterates .items() (list) — will break.

getattr(selector, "matchExpressions") is typically a list; .items() will raise. Iterate directly.

Apply this diff:

-        if getattr(selector, "matchExpressions", None):
-            label_filters += [
-                ClusterLoader._get_match_expression_filter(expression) for expression in getattr(selector, "matchExpressions").items()
-            ]
+        if getattr(selector, "matchExpressions", None):
+            label_filters += [
+                ClusterLoader._get_match_expression_filter(expression)
+                for expression in getattr(selector, "matchExpressions")
+            ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a07d54d and b79d7fe.

📒 Files selected for processing (1)
  • robusta_krr/core/integrations/kubernetes/__init__.py (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
robusta_krr/core/integrations/kubernetes/__init__.py (3)
robusta_krr/core/models/objects.py (2)
  • selector (76-83)
  • K8sObjectData (38-107)
robusta_krr/utils/object_like_dict.py (2)
  • get (25-26)
  • ObjectLikeDict (1-29)
robusta_krr/core/models/allocations.py (2)
  • ResourceAllocations (52-106)
  • from_container (79-106)
🔇 Additional comments (5)
robusta_krr/core/integrations/kubernetes/__init__.py (5)

110-111: CNPG discovery wired into scan flow — LGTM.

Including CNPG clusters in the gathered workloads aligns with the other resource paths.


331-335: Graceful handling when CNPG CRD is unavailable — LGTM.

Adding CNPGCluster to the non-fatal API-not-available list is consistent with other CRDs.


341-363: CNPG listing via CustomObjectsApi — LGTM.

The wrapper with ObjectLikeDict matches existing CRD patterns; passing the item through for CNPG-specific allocation handling is appropriate.


218-226: CNPG allocations likely incorrect: map string keys to ResourceType.

spec.resources uses "cpu"/"memory" keys, but downstream expects ResourceType keys; otherwise allocations resolve to None in reports.

Apply this diff to map keys properly:

         # Special handling for CNPGCluster
         if kind == "CNPGCluster":
-            resources = getattr(item.spec, "resources", None) or item.get("spec", {}).get("resources", {})
-            allocations = ResourceAllocations(
-                requests=resources.get("requests", {}),
-                limits=resources.get("limits", {}),
-            )
+            resources = getattr(item.spec, "resources", None) or item.get("spec", {}).get("resources", {}) or {}
+            req = resources.get("requests", {}) or {}
+            lim = resources.get("limits", {}) or {}
+            allocations = ResourceAllocations(
+                requests={
+                    ResourceType.CPU: req.get("cpu"),
+                    ResourceType.Memory: req.get("memory"),
+                },
+                limits={
+                    ResourceType.CPU: lim.get("cpu"),
+                    ResourceType.Memory: lim.get("memory"),
+                },
+            )
             container_name = "postgres"
         else:
             allocations = ResourceAllocations.from_container(container)
             container_name = getattr(container, "name", None)

Also add the missing import:

-from robusta_krr.core.models.result import ResourceAllocations
+from robusta_krr.core.models.result import ResourceAllocations, ResourceType
#!/bin/bash
# Check call sites expect ResourceType keys (to validate why mapping is needed)
rg -nP --type=py -C2 '\ballocations\.(requests|limits)\s*\[\s*ResourceType\.' 

Also applies to: 249-251


150-152: No change required — selector cnpg.io/cluster is correct

CloudNativePG pods use the label key cnpg.io/cluster to associate pods with a cluster (confirmed in CloudNativePG docs/quickstart).

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.

1 participant