Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

172 lines
5.1 KiB
Python

"""Canonical OpenSRE alert payload normalization.
This module converts source-specific alert fields into a stable in-memory
shape so downstream nodes can consume one format regardless of origin.
"""
from __future__ import annotations
from collections.abc import Iterable
from typing import Any
from core.domain.alerts.fields import (
alert_name_value,
first_present,
pipeline_name_value,
severity_value,
)
def _as_mapping(value: Any) -> dict[str, Any]:
if isinstance(value, dict):
return dict(value)
return {}
def _to_text(value: Any) -> str | None:
if value is None:
return None
text = str(value).strip()
return text or None
def _parse_tags(value: Any) -> dict[str, str]:
"""Parse Datadog-like tags into a dictionary.
Supports:
- comma-separated strings: "env:prod,service:payments"
- list[str]: ["env:prod", "service:payments"]
- dict[str, Any]: {"env": "prod"}
"""
if isinstance(value, dict):
return {str(k): str(v) for k, v in value.items() if _to_text(k) and _to_text(v)}
items: Iterable[str]
if isinstance(value, str):
items = [part.strip() for part in value.split(",") if part.strip()]
elif isinstance(value, list):
items = [str(part).strip() for part in value if _to_text(part)]
else:
return {}
parsed: dict[str, str] = {}
for item in items:
if ":" not in item:
continue
key, raw_value = item.split(":", 1)
key_text = _to_text(key)
value_text = _to_text(raw_value)
if key_text and value_text:
parsed[key_text] = value_text
return parsed
def _coerce_pid(value: Any) -> int | None:
if value is None:
return None
if isinstance(value, int):
return value if value >= 0 else None
if isinstance(value, float) and value.is_integer():
pid = int(value)
return pid if pid >= 0 else None
text = _to_text(value)
if text is None:
return None
try:
pid = int(text)
except ValueError:
return None
return pid if pid >= 0 else None
def normalize_alert_payload(raw_alert: dict[str, Any]) -> dict[str, Any]:
"""Normalize an alert payload to canonical OpenSRE alert format.
The returned payload preserves original fields and also adds:
- ``commonLabels`` / ``commonAnnotations`` as dictionaries
- top-level ``process_name`` / ``cmdline`` / ``pid`` when discovered
- ``canonical_alert`` containing the normalized, vendor-agnostic shape
"""
normalized = dict(raw_alert)
raw_common_labels = normalized.get("commonLabels")
labels = (
_as_mapping(raw_common_labels)
if raw_common_labels is not None
else _as_mapping(normalized.get("labels"))
)
tags = _parse_tags(normalized.get("tags"))
if tags:
labels = {**tags, **labels}
raw_common_annotations = normalized.get("commonAnnotations")
annotations = (
_as_mapping(raw_common_annotations)
if raw_common_annotations is not None
else _as_mapping(normalized.get("annotations"))
)
normalized["commonLabels"] = labels
normalized["commonAnnotations"] = annotations
process_name = _to_text(
first_present(
normalized.get("process_name"),
normalized.get("processName"),
normalized.get("process.name"),
normalized.get("procname"),
labels.get("process_name"),
labels.get("process"),
annotations.get("process_name"),
)
)
cmdline = _to_text(
first_present(
normalized.get("cmdline"),
normalized.get("command"),
normalized.get("command_line"),
normalized.get("process.cmdline"),
normalized.get("process_command_line"),
labels.get("cmdline"),
annotations.get("cmdline"),
)
)
pid = _coerce_pid(
first_present(
normalized.get("pid"),
normalized.get("process_id"),
normalized.get("process.pid"),
labels.get("pid"),
annotations.get("pid"),
)
)
if process_name and not _to_text(normalized.get("process_name")):
normalized["process_name"] = process_name
if cmdline and not _to_text(normalized.get("cmdline")):
normalized["cmdline"] = cmdline
if pid is not None and _coerce_pid(normalized.get("pid")) is None:
normalized["pid"] = pid
canonical_alert = {
"schema": "opensre.alert.v1",
"alert_name": _to_text(
alert_name_value(normalized, labels=labels, annotations=annotations)
),
"pipeline_name": _to_text(
pipeline_name_value(normalized, labels=labels, annotations=annotations)
),
"severity": _to_text(severity_value(normalized, labels=labels)),
"alert_source": _to_text(normalized.get("alert_source")),
"labels": dict(labels),
"annotations": dict(annotations),
"process": {
"name": process_name,
"cmdline": cmdline,
"pid": pid,
},
}
normalized["canonical_alert"] = canonical_alert
return normalized