Skip to content

Commit 44fdadf

Browse files
committed
Addressing comments
1 parent 4625c4c commit 44fdadf

4 files changed

Lines changed: 148 additions & 166 deletions

File tree

src/secops/chronicle/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@
334334
create_watchlist as _create_watchlist,
335335
update_watchlist as _update_watchlist,
336336
)
337-
from secops.chronicle.parser_validation import (
337+
from secops.chronicle.parser import (
338338
get_analysis_report as _get_analysis_report,
339339
trigger_github_checks as _trigger_github_checks,
340340
)

src/secops/chronicle/parser.py

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
import base64
1818
import json
19+
import logging
20+
import re
1921
from 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
2427
MAX_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+
)

src/secops/chronicle/parser_validation.py

Lines changed: 0 additions & 159 deletions
This file was deleted.

src/secops/chronicle/utils/request_utils.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,14 @@ def chronicle_request(
230230
# - RPC-style methods e.g: ":validateQuery" -> .../{instance_id}:validateQuery
231231
# - Legacy paths e.g: "legacy:..." -> .../{instance_id}/legacy:...
232232
# - normal paths e.g: "curatedRules/..." -> .../{instance_id}/curatedRules/...
233-
base = f"{client.base_url(api_version)}/{client.instance_id}"
233+
base = f"{client.base_url(api_version)}"
234234

235-
if endpoint_path.startswith(":"):
236-
url = f"{base}{endpoint_path}"
235+
if endpoint_path.startswith("projects/"):
236+
url = f"{base}/{endpoint_path}"
237+
elif endpoint_path.startswith(":"):
238+
url = f"{base}/{client.instance_id}{endpoint_path}"
237239
else:
238-
url = f'{base}/{endpoint_path.lstrip("/")}'
240+
url = f'{base}/{client.instance_id}/{endpoint_path.lstrip("/")}'
239241

240242
try:
241243
response = client.session.request(

0 commit comments

Comments
 (0)