1616
1717import base64
1818import json
19+ import logging
20+ import re
1921from typing import Any
2022
21- from secops .exceptions import APIError
23+ from secops .chronicle .utils .request_utils import chronicle_request
24+ from secops .exceptions import APIError , SecOpsError
2225
2326# Constants for size limits
2427MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB per log
@@ -254,6 +257,8 @@ def list_parsers(
254257 page_size : int | None = None ,
255258 page_token : str | None = None ,
256259 filter : str = None , # pylint: disable=redefined-builtin
260+ instance_id : str | None = None ,
261+ api_version : str | None = None ,
257262) -> list [Any ] | dict [str , Any ]:
258263 """List parsers.
259264
@@ -278,8 +283,13 @@ def list_parsers(
278283 parsers = []
279284
280285 while more :
286+ eff_instance_id = instance_id or client .instance_id
287+ if api_version :
288+ base_url = client .base_url (version = api_version )
289+ else :
290+ base_url = client .base_url
281291 url = (
282- f"{ client . base_url } /{ client . instance_id } "
292+ f"{ base_url } /{ eff_instance_id } "
283293 f"/logTypes/{ log_type } /parsers"
284294 )
285295
@@ -489,3 +499,132 @@ def run_parser(
489499 print (f"Warning: Failed to parse statedump: { e } " )
490500
491501 return result
502+
503+
504+ def trigger_github_checks (
505+ client : "ChronicleClient" ,
506+ associated_pr : str ,
507+ log_type : str ,
508+ customer_id : str | None = None ,
509+ timeout : int = 60 ,
510+ ) -> dict [str , Any ]:
511+ """Trigger GitHub checks for a parser.
512+
513+ Args:
514+ client: ChronicleClient instance
515+ associated_pr: The PR string (e.g., "owner/repo/pull/123").
516+ log_type: The string name of the LogType enum.
517+ customer_id: Optional. The customer UUID string. Defaults to client
518+ configured ID.
519+ timeout: Optional RPC timeout in seconds (default: 60).
520+
521+ Returns:
522+ Dictionary containing the response details.
523+
524+ Raises:
525+ SecOpsError: If input is invalid.
526+ APIError: If the API request fails.
527+ """
528+
529+ if not isinstance (log_type , str ) or len (log_type .strip ()) < 2 :
530+ raise SecOpsError ("log_type must be a valid string of length >= 2" )
531+ if customer_id is not None :
532+ if not isinstance (customer_id , str ) or not re .match (
533+ r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
534+ r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" ,
535+ customer_id ,
536+ ):
537+ raise SecOpsError ("customer_id must be a valid UUID string" )
538+ if not isinstance (associated_pr , str ) or not associated_pr .strip ():
539+ raise SecOpsError ("associated_pr must be a non-empty string" )
540+
541+ pr_parts = associated_pr .split ("/" )
542+ if len (pr_parts ) != 4 or pr_parts [2 ] != "pull" or not pr_parts [3 ].isdigit ():
543+ raise SecOpsError (
544+ "associated_pr must be in the format 'owner/repo/pull/<number>'"
545+ )
546+ if not isinstance (timeout , int ) or timeout < 0 :
547+ raise SecOpsError ("timeout must be a non-negative integer" )
548+
549+ eff_customer_id = customer_id or client .customer_id
550+ instance_id = client .instance_id
551+ if eff_customer_id and eff_customer_id != client .customer_id :
552+ region = "us" if client .region in ["dev" , "staging" ] else client .region
553+ instance_id = (
554+ f"projects/{ client .project_id } /locations/"
555+ f"{ region } /instances/{ eff_customer_id } "
556+ )
557+
558+ try :
559+ parsers = list_parsers (
560+ client ,
561+ log_type = log_type ,
562+ instance_id = instance_id ,
563+ api_version = "v1alpha" ,
564+ )
565+ except APIError as e :
566+ raise APIError (
567+ f"Failed to fetch parsers for log type { log_type } : { e } "
568+ ) from e
569+
570+ if not parsers :
571+ logging .info (
572+ "No parsers found for log type %s. Using fallback parser ID." ,
573+ log_type ,
574+ )
575+ parser_name = f"{ instance_id } /logTypes/{ log_type } /parsers/-"
576+ else :
577+ if len (parsers ) > 1 :
578+ logging .warning (
579+ "Multiple parsers found for log type %s. Using the first one." ,
580+ log_type ,
581+ )
582+ parser_name = parsers [0 ]["name" ]
583+
584+ endpoint_path = f"{ parser_name } :runAnalysis"
585+ payload = {
586+ "report_type" : "GITHUB_PARSER_VALIDATION" ,
587+ "pull_request" : associated_pr ,
588+ }
589+
590+ return chronicle_request (
591+ client = client ,
592+ method = "POST" ,
593+ api_version = "v1alpha" ,
594+ endpoint_path = endpoint_path ,
595+ json = payload ,
596+ timeout = timeout ,
597+ )
598+
599+
600+ def get_analysis_report (
601+ client : "ChronicleClient" ,
602+ name : str ,
603+ timeout : int = 60 ,
604+ ) -> dict [str , Any ]:
605+ """Get a parser analysis report.
606+
607+ Args:
608+ client: ChronicleClient instance
609+ name: The full resource name of the analysis report.
610+ timeout: Optional timeout in seconds (default: 60).
611+
612+ Returns:
613+ Dictionary containing the analysis report.
614+
615+ Raises:
616+ SecOpsError: If input is invalid.
617+ APIError: If the API request fails.
618+ """
619+ if not isinstance (name , str ) or len (name .strip ()) < 5 :
620+ raise SecOpsError ("name must be a valid string" )
621+ if not isinstance (timeout , int ) or timeout < 0 :
622+ raise SecOpsError ("timeout must be a non-negative integer" )
623+
624+ return chronicle_request (
625+ client = client ,
626+ method = "GET" ,
627+ api_version = "v1alpha" ,
628+ endpoint_path = name ,
629+ timeout = timeout ,
630+ )
0 commit comments