Skip to content

Commit b96bfdd

Browse files
authored
[ROB-1588] Push results to runner on success or failure (#442)
tested in cluster and cli in cluster verified errors in multiple places in code are sent to scan failure in the db
1 parent 75cdae7 commit b96bfdd

File tree

3 files changed

+100
-4
lines changed

3 files changed

+100
-4
lines changed

robusta_krr/core/models/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class Config(pd.BaseSettings):
6060
width: Optional[int] = pd.Field(None, ge=1)
6161
show_severity: bool = True
6262

63+
# Publishing to url settings
64+
publish_scan_url: Optional[str] = pd.Field(None)
65+
start_time: Optional[str] = pd.Field(None)
66+
scan_id: Optional[str] = pd.Field(None)
67+
6368
# Output Settings
6469
file_output: Optional[str] = pd.Field(None)
6570
file_output_dynamic: bool = pd.Field(False)

robusta_krr/core/runner.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
import math
44
import os
55
import sys
6+
import time
67
import warnings
78
from concurrent.futures import ThreadPoolExecutor
89
from typing import Optional, Union
910
from datetime import timedelta, datetime
1011
from prometrix import PrometheusNotFound
1112
from rich.console import Console
1213
from slack_sdk import WebClient
13-
14+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
15+
import requests
16+
import json
17+
import traceback
1418
from robusta_krr.core.abstract.strategies import ResourceRecommendation, RunResult
1519
from robusta_krr.core.integrations.kubernetes import KubernetesLoader
1620
from robusta_krr.core.integrations.prometheus import ClusterNotSpecifiedException, PrometheusMetricsLoader
@@ -104,6 +108,8 @@ def _process_result(self, result: Result) -> None:
104108
result.errors = self.errors
105109

106110
Formatter = settings.Formatter
111+
112+
self._send_result(settings.publish_scan_url, settings.start_time, settings.scan_id, result)
107113
formatted = result.format(Formatter)
108114
rich = getattr(Formatter, "__rich_console__", False)
109115

@@ -329,6 +335,7 @@ async def run(self) -> int:
329335
except Exception as e:
330336
logger.error(f"Could not load kubernetes configuration: {e}")
331337
logger.error("Try to explicitly set --context and/or --kubeconfig flags.")
338+
publish_error(f"Could not load kubernetes configuration: {e}")
332339
return 1 # Exit with error
333340

334341
try:
@@ -348,9 +355,71 @@ async def run(self) -> int:
348355
self._process_result(result)
349356
except (ClusterNotSpecifiedException, CriticalRunnerException) as e:
350357
logger.critical(e)
358+
publish_error(traceback.format_exc())
351359
return 1 # Exit with error
352360
except Exception:
353361
logger.exception("An unexpected error occurred")
362+
publish_error(traceback.format_exc())
354363
return 1 # Exit with error
355364
else:
356365
return 0 # Exit with success
366+
367+
def _send_result(self, url: str, start_time: datetime, scan_id: str, result: Result):
368+
result_dict = json.loads(result.json(indent=2))
369+
_send_scan_payload(url, scan_id, start_time, result_dict, is_error=False)
370+
371+
def publish_input_error(url: str, scan_id: str, start_time: str, error: str):
372+
_send_scan_payload(url, scan_id, start_time, error, is_error=True)
373+
374+
def publish_error(error: str):
375+
_send_scan_payload(settings.publish_scan_url, settings.scan_id, settings.start_time, error, is_error=True)
376+
377+
@retry(
378+
stop=stop_after_attempt(3),
379+
wait=wait_exponential(multiplier=1, min=1, max=8),
380+
reraise=True
381+
)
382+
def _post_scan_request(url: str, headers: dict, payload: dict, scan_id: str, is_error: bool):
383+
logger_msg = "Sending error scan" if is_error else "Sending scan"
384+
logger.info(f"{logger_msg} for scan_id={scan_id} to url={url}")
385+
response = requests.post(url, headers=headers, json=payload)
386+
logger.info(f"scan_id={scan_id} | Status code: {response.status_code}")
387+
logger.info(f"scan_id={scan_id} | Response body: {response.text}")
388+
return response
389+
390+
391+
def _send_scan_payload(
392+
url: str,
393+
scan_id: str,
394+
start_time: Union[str, datetime],
395+
result_data: Union[str, dict],
396+
is_error: bool = False
397+
):
398+
if not url or not scan_id or not start_time:
399+
logger.debug(f"Missing required parameters: url={bool(url)}, scan_id={bool(scan_id)}, start_time={bool(start_time)}")
400+
return
401+
402+
logger.debug(f"Preparing to send scan payload. scan_id={scan_id}, is_error={is_error}")
403+
404+
headers = {"Content-Type": "application/json"}
405+
406+
if isinstance(start_time, datetime):
407+
logger.debug(f"Converting datetime to ISO format for scan_id={scan_id}")
408+
start_time = start_time.isoformat()
409+
410+
action_request = {
411+
"action_name": "process_scan",
412+
"action_params": {
413+
"result": result_data,
414+
"scan_type": "krr",
415+
"scan_id": scan_id,
416+
"start_time": start_time,
417+
}
418+
}
419+
420+
try:
421+
_post_scan_request(url, headers, action_request, scan_id, is_error)
422+
except requests.exceptions.RequestException as e:
423+
logger.error(f"scan_id={scan_id} | All retry attempts failed due to RequestException: {e}", exc_info=True)
424+
except Exception as e:
425+
logger.error(f"scan_id={scan_id} | Unexpected error after retries: {e}", exc_info=True)

robusta_krr/main.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from robusta_krr.core.abstract import formatters
1818
from robusta_krr.core.abstract.strategies import BaseStrategy
1919
from robusta_krr.core.models.config import Config
20-
from robusta_krr.core.runner import Runner
20+
from robusta_krr.core.runner import Runner, publish_input_error
2121
from robusta_krr.utils.version import get_version
2222

2323
app = typer.Typer(
@@ -266,6 +266,24 @@ def run_strategy(
266266
help="Send to output to a slack channel, must have SLACK_BOT_TOKEN",
267267
rich_help_panel="Output Settings",
268268
),
269+
publish_scan_url: Optional[str] = typer.Option(
270+
None,
271+
"--publish_scan_url",
272+
help="Sends the output to a robusta_runner instance",
273+
rich_help_panel="Publish Scan Settings",
274+
),
275+
start_time: Optional[str] = typer.Option(
276+
None,
277+
"--start_time",
278+
help="Start time of the scan",
279+
rich_help_panel="Publish Scan Settings",
280+
),
281+
scan_id: Optional[str] = typer.Option(
282+
None,
283+
"--scan_id",
284+
help="A UUID scan identifier",
285+
rich_help_panel="Publish Scan Settings",
286+
),
269287
**strategy_args,
270288
) -> None:
271289
f"""Run KRR using the `{_strategy_name}` strategy"""
@@ -310,10 +328,14 @@ def run_strategy(
310328
show_severity=show_severity,
311329
strategy=_strategy_name,
312330
other_args=strategy_args,
313-
)
331+
publish_scan_url=publish_scan_url,
332+
start_time=start_time,
333+
scan_id=scan_id,
334+
)
314335
Config.set_config(config)
315-
except ValidationError:
336+
except ValidationError as e:
316337
logger.exception("Error occured while parsing arguments")
338+
publish_input_error( publish_scan_url, start_time, scan_id, str(e))
317339
else:
318340
runner = Runner()
319341
exit_code = asyncio.run(runner.run())

0 commit comments

Comments
 (0)