-
Notifications
You must be signed in to change notification settings - Fork 244
Support cloudnative-pg (CNPG) clusters #473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds 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
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...]
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...]
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧬 Code graph analysis (1)robusta_krr/core/integrations/kubernetes/__init__.py (3)
🔇 Additional comments (7)
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. Comment |
There was a problem hiding this 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
📒 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
), sofrom robusta_krr.core.models.result import ResourceAllocations
will succeed at runtime.
There was a problem hiding this 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
📒 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 — selectorcnpg.io/cluster
is correctCloudNativePG pods use the label key
cnpg.io/cluster
to associate pods with a cluster (confirmed in CloudNativePG docs/quickstart).
Related to #139