33import math
44import os
55import sys
6+ import time
67import warnings
78from concurrent .futures import ThreadPoolExecutor
89from typing import Optional , Union
910from datetime import timedelta , datetime
1011from prometrix import PrometheusNotFound
1112from rich .console import Console
1213from 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
1418from robusta_krr .core .abstract .strategies import ResourceRecommendation , RunResult
1519from robusta_krr .core .integrations .kubernetes import KubernetesLoader
1620from 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 )
0 commit comments