Skip to content
Merged
2 changes: 1 addition & 1 deletion docs/data-sources/builtin-toolsets/robusta.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ holmes:
| Tool Name | Description |
|-----------|-------------|
| fetch_finding_by_id | Fetches detailed metadata about a specific Robusta finding (alerts, deployment updates, etc.) including historical context |
| fetch_configuration_changes | Retrieves all configuration changes in a given time range, optionally filtered by namespace or workload |
| fetch_configuration_changes_metadata | Retrieves all configuration changes in a given time range, optionally filtered by namespace or workload |
| fetch_resource_recommendation | Provides resource optimization recommendations based on actual historical usage for Deployments, StatefulSets, DaemonSets, and Jobs |

## Use Cases
Expand Down
75 changes: 33 additions & 42 deletions holmes/core/supabase_dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,70 +237,61 @@ def get_resource_recommendation(
logging.exception("Supabase error while retrieving efficiency data")
return None

def get_configuration_changes(
self, start_datetime: str, end_datetime: str
def get_configuration_changes_metadata(
self,
start_datetime: str,
end_datetime: str,
limit: int = 100,
workload: Optional[str] = None,
ns: Optional[str] = None,
) -> Optional[List[Dict]]:
if not self.enabled:
return []

try:
changes_response = (
query = (
self.client.table(ISSUES_TABLE)
.select("id", "subject_name", "subject_namespace", "description")
.select(
"id",
"title",
"subject_name",
"subject_namespace",
"subject_type",
"description",
"starts_at",
"ends_at",
)
.eq("account_id", self.account_id)
.eq("cluster", self.cluster)
.eq("finding_type", "configuration_change")
.gte("creation_date", start_datetime)
.lte("creation_date", end_datetime)
.execute()
.limit(limit)
)
if not len(changes_response.data):
return None

except Exception:
logging.exception("Supabase error while retrieving change data")
return None
if workload:
query.eq("subject_name", workload)
if ns:
query.eq("subject_namespace", ns)

changes_ids = [change["id"] for change in changes_response.data]
try:
change_data_response = (
self.client.table(EVIDENCE_TABLE)
.select("*")
.eq("account_id", self.account_id)
.in_("issue_id", changes_ids)
.not_.in_("enrichment_type", ENRICHMENT_BLACKLIST)
.execute()
)
if not len(change_data_response.data):
res = query.execute()
if not res.data:
return None

truncate_evidences_entities_if_necessary(change_data_response.data)

except Exception:
logging.exception("Supabase error while retrieving change content")
logging.exception("Supabase error while retrieving change data")
return None

changes_data = []
change_data_map = {
change["issue_id"]: change for change in change_data_response.data
}

for change in changes_response.data:
change_content = change_data_map.get(change["id"])
if change_content:
changes_data.append(
{
"change": change_content["data"],
"evidence_id": change_content["id"],
**change,
}
)

logging.debug(
"Change history for %s-%s: %s", start_datetime, end_datetime, changes_data
"Change history metadata for %s-%s workload %s in ns %s: %s",
start_datetime,
end_datetime,
workload,
ns,
res.data,
)

return changes_data
return res.data

def unzip_evidence_file(self, data):
try:
Expand Down
43 changes: 35 additions & 8 deletions holmes/plugins/toolsets/robusta/robusta.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
END_TIME = "end_datetime"
NAMESPACE = "namespace"
WORKLOAD = "workload"
DEFAULT_LIMIT_CHANGE_ROWS = 100
MAX_LIMIT_CHANGE_ROWS = 200


class FetchRobustaFinding(Tool):
Expand All @@ -27,7 +29,7 @@ class FetchRobustaFinding(Tool):
def __init__(self, dal: Optional[SupabaseDal]):
super().__init__(
name="fetch_finding_by_id",
description="Fetches a robusta finding. Findings are events, like a Prometheus alert or a deployment update",
description="Fetches a robusta finding. Findings are events, like a Prometheus alert or a deployment update and configuration change.",
parameters={
PARAM_FINDING_ID: ToolParameter(
description="The id of the finding to fetch",
Expand Down Expand Up @@ -75,7 +77,7 @@ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolRes
)

def get_parameterized_one_liner(self, params: Dict) -> str:
return "Robusta: Fetch Alert Metadata"
return f"Robusta: Fetch finding data {params}"


class FetchResourceRecommendation(Tool):
Expand Down Expand Up @@ -142,13 +144,17 @@ def get_parameterized_one_liner(self, params: Dict) -> str:
return f"Robusta: Check Historical Resource Utilization: ({str(params)})"


class FetchConfigurationChanges(Tool):
class FetchConfigurationChangesMetadata(Tool):
_dal: Optional[SupabaseDal]

def __init__(self, dal: Optional[SupabaseDal]):
super().__init__(
name="fetch_configuration_changes",
description="Fetch configuration changes in a given time range. By default, fetch all cluster changes. Can be filtered on a given namespace or a specific workload",
name="fetch_configuration_changes_metadata",
description=(
"Fetch configuration changes metadata in a given time range. "
"By default, fetch all cluster changes. Can be filtered on a given namespace or a specific workload. "
"Use fetch_finding_by_id to get detailed change of one specific configuration change."
),
parameters={
START_TIME: ToolParameter(
description="The starting time boundary for the search period. String in RFC3339 format.",
Expand All @@ -160,15 +166,36 @@ def __init__(self, dal: Optional[SupabaseDal]):
type="string",
required=True,
),
"namespace": ToolParameter(
description="The Kubernetes namespace name for filtering configuration changes",
type="string",
required=False,
),
"workload": ToolParameter(
description="The kubernetes workload name for filtering configuration changes. Deployment name or Pod name for example.",
type="string",
required=False,
),
"limit": ToolParameter(
description=f"Maximum number of rows to return. Default is {DEFAULT_LIMIT_CHANGE_ROWS} and the maximum is 200",
type="integer",
required=False,
),
},
)
self._dal = dal

def _fetch_change_history(self, params: Dict) -> Optional[List[Dict]]:
if self._dal and self._dal.enabled:
return self._dal.get_configuration_changes(
return self._dal.get_configuration_changes_metadata(
start_datetime=params["start_datetime"],
end_datetime=params["end_datetime"],
limit=min(
params.get("limit") or DEFAULT_LIMIT_CHANGE_ROWS,
MAX_LIMIT_CHANGE_ROWS,
),
ns=params.get("namespace"),
workload=params.get("workload"),
)
return None

Expand Down Expand Up @@ -197,7 +224,7 @@ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolRes
)

def get_parameterized_one_liner(self, params: Dict) -> str:
return "Robusta: Search Change History"
return f"Robusta: Search Change History {params}"


class RobustaToolset(Toolset):
Expand All @@ -219,7 +246,7 @@ def __init__(self, dal: Optional[SupabaseDal]):
prerequisites=[dal_prereq],
tools=[
FetchRobustaFinding(dal),
FetchConfigurationChanges(dal),
FetchConfigurationChangesMetadata(dal),
FetchResourceRecommendation(dal),
],
tags=[
Expand Down
7 changes: 4 additions & 3 deletions holmes/plugins/toolsets/robusta/robusta_instructions.jinja2
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Configuration and alerts history
* Use fetch_configuration_changes to get historical configuration changes
* You must ALWAYS call fetch_configuration_changes when investigating an alert
* Never respond without calling fetch_configuration_changes
* Use fetch_configuration_changes_metadata to get historical configuration changes in a cluster or for a specific workload.
* If a change seems important to the investigation, Use fetch_finding_by_id with the configuration change ID to get full details of the change.
* You must ALWAYS call fetch_configuration_changes_metadata when investigating an alert
* Never respond without calling fetch_configuration_changes_metadata
* When investigating an alert, look at historical configuration changes that happen 4 hours before the alert started
* If you found a change that caused the alert, you MUST write: 'The issue was introduced by ...' with a short description of the change, and the date of it.
For example:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes","match_params":{"start_datetime":"2025-06-12T00:00:00Z","end_datetime":"2025-06-12T23:59:59Z"}}
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes_metadata","match_params":{"start_datetime":"2025-06-12T00:00:00Z","end_datetime":"2025-06-12T23:59:59Z"}}
{"schema_version": "robusta:v1.0.0", "status": "no_data", "error": null, "return_code": null, "url": null, "invocation": null, "params": {"start_datetime": "2025-06-12T00:00:00Z", "end_datetime": "2025-06-12T23:59:59Z"}}
[
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes"}
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes_metadata"}
{"schema_version": "robusta:v1.0.0", "status": "no_data", "error": null, "return_code": null, "data": null, "url": null, "invocation": null, "params": {"start_datetime": "2023-10-03T08:00:00Z", "end_datetime": "2023-10-03T12:00:00Z"}}
Could not find changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes"}
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes_metadata"}
{"schema_version": "robusta:v1.0.0", "status": "no_data", "error": null, "return_code": null, "data": null, "url": null, "invocation": null, "params": {"start_datetime": "2025-01-27T02:06:27Z", "end_datetime": "2025-01-27T06:06:27Z"}}
Could not find changes for {'start_datetime': '2025-01-27T02:06:27Z', 'end_datetime': '2025-01-27T06:06:27Z'}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes"}
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes_metadata"}
{"schema_version": "robusta:v1.0.0", "status": "no_data", "error": null, "return_code": null, "data": null, "url": null, "invocation": null, "params": {"start_datetime": "2024-11-29T04:40:27Z", "end_datetime": "2024-11-29T08:40:27Z"}}
Could not find changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes"}
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes_metadata"}
{"schema_version": "robusta:v1.0.0", "status": "no_data", "error": null, "return_code": null, "data": null, "url": null, "invocation": null, "params": {"start_datetime": "2025-03-17T08:36:49.299Z", "end_datetime": "2025-03-17T12:36:49.299Z"}}
Could not find changes
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes"}
{"toolset_name":"robusta","tool_name":"fetch_configuration_changes_metadata"}
{"schema_version": "robusta:v1.0.0", "status": "no_data", "error": null, "return_code": null, "data": null, "url": null, "invocation": null, "params": {"start_datetime": "2025-08-25T01:50:00Z", "end_datetime": "2025-08-25T05:50:00Z"}}
Loading