4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
1788 lines
74 KiB
Python
1788 lines
74 KiB
Python
"""Shared integration catalog for normalization and resolution."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from config.config import get_tracer_base_url
|
|
from config.llm_credentials import resolve_env_credential
|
|
from integrations.airflow.config import airflow_config_from_env
|
|
from integrations.airflow.config import classify as _classify_airflow
|
|
from integrations.alertmanager import classify as _classify_alertmanager
|
|
from integrations.argocd import classify as _classify_argocd
|
|
from integrations.aws import classify as _classify_aws
|
|
from integrations.azure import classify as _classify_azure
|
|
from integrations.azure_sql import build_azure_sql_config
|
|
from integrations.azure_sql import classify as _classify_azure_sql
|
|
from integrations.betterstack import build_betterstack_config
|
|
from integrations.betterstack import classify as _classify_betterstack
|
|
from integrations.bitbucket import classify as _classify_bitbucket
|
|
from integrations.config_models import (
|
|
AlertmanagerIntegrationConfig,
|
|
ArgoCDIntegrationConfig,
|
|
AWSIntegrationConfig,
|
|
CoralogixIntegrationConfig,
|
|
DatadogIntegrationConfig,
|
|
DiscordBotConfig,
|
|
GrafanaIntegrationConfig,
|
|
GroundcoverIntegrationConfig,
|
|
HelmIntegrationConfig,
|
|
HoneycombIntegrationConfig,
|
|
IncidentIoIntegrationConfig,
|
|
JiraIntegrationConfig,
|
|
OpsGenieIntegrationConfig,
|
|
PagerDutyIntegrationConfig,
|
|
SlackWebhookConfig,
|
|
SMTPIntegrationConfig,
|
|
SplunkIntegrationConfig,
|
|
TelegramBotConfig,
|
|
TwilioIntegrationConfig,
|
|
VictoriaLogsIntegrationConfig,
|
|
WhatsAppConfig,
|
|
)
|
|
from integrations.coralogix import classify as _classify_coralogix
|
|
from integrations.dagster import build_dagster_config
|
|
from integrations.dagster import classify as _classify_dagster
|
|
from integrations.datadog import classify as _classify_datadog
|
|
from integrations.discord import classify as _classify_discord
|
|
from integrations.effective_models import EffectiveIntegrations
|
|
from integrations.github.mcp import build_github_mcp_config
|
|
from integrations.github.mcp import classify as _classify_github
|
|
from integrations.gitlab import DEFAULT_GITLAB_BASE_URL, build_gitlab_config
|
|
from integrations.gitlab import classify as _classify_gitlab
|
|
from integrations.grafana import classify as _classify_grafana
|
|
from integrations.groundcover import classify as _classify_groundcover
|
|
from integrations.helm import classify as _classify_helm
|
|
from integrations.honeycomb import classify as _classify_honeycomb
|
|
from integrations.incident_io import classify as _classify_incident_io
|
|
from integrations.jenkins import classify as _classify_jenkins
|
|
from integrations.jenkins import jenkins_config_from_env
|
|
from integrations.jira import classify as _classify_jira
|
|
from integrations.mariadb import build_mariadb_config
|
|
from integrations.mariadb import classify as _classify_mariadb
|
|
from integrations.mongodb import build_mongodb_config
|
|
from integrations.mongodb import classify as _classify_mongodb
|
|
from integrations.mongodb_atlas import build_mongodb_atlas_config
|
|
from integrations.mongodb_atlas import classify as _classify_mongodb_atlas
|
|
from integrations.mysql import build_mysql_config
|
|
from integrations.mysql import classify as _classify_mysql
|
|
from integrations.openclaw import build_openclaw_config
|
|
from integrations.openclaw import classify as _classify_openclaw
|
|
from integrations.openobserve import classify as _classify_openobserve
|
|
from integrations.opensearch import classify as _classify_opensearch
|
|
from integrations.opsgenie import classify as _classify_opsgenie
|
|
from integrations.pagerduty import classify as _classify_pagerduty
|
|
from integrations.postgresql import build_postgresql_config
|
|
from integrations.postgresql import classify as _classify_postgresql
|
|
from integrations.posthog_mcp import DEFAULT_POSTHOG_MCP_URL, build_posthog_mcp_config
|
|
from integrations.posthog_mcp import classify as _classify_posthog_mcp
|
|
from integrations.prefect import classify as _classify_prefect
|
|
from integrations.rabbitmq import build_rabbitmq_config
|
|
from integrations.rabbitmq import classify as _classify_rabbitmq
|
|
from integrations.rds import classify as _classify_rds
|
|
from integrations.rds import rds_config_from_env
|
|
from integrations.redis import classify as _classify_redis
|
|
from integrations.redis import redis_config_from_env
|
|
from integrations.registry import (
|
|
DIRECT_CLASSIFIED_EFFECTIVE_SERVICES,
|
|
SKIP_CLASSIFIED_SERVICES,
|
|
family_key,
|
|
service_key,
|
|
)
|
|
from integrations.sentry import build_sentry_config
|
|
from integrations.sentry import classify as _classify_sentry
|
|
from integrations.sentry_mcp import DEFAULT_SENTRY_MCP_URL, build_sentry_mcp_config
|
|
from integrations.sentry_mcp import classify as _classify_sentry_mcp
|
|
from integrations.signoz import classify as _classify_signoz
|
|
from integrations.signoz import signoz_config_from_env
|
|
from integrations.smtp import classify as _classify_smtp
|
|
from integrations.snowflake import classify as _classify_snowflake
|
|
from integrations.splunk import classify as _classify_splunk
|
|
from integrations.store import _STRUCTURAL_RECORD_FIELDS, load_integrations
|
|
from integrations.supabase import build_supabase_config
|
|
from integrations.supabase import classify as _classify_supabase
|
|
from integrations.telegram import classify as _classify_telegram
|
|
from integrations.tempo import classify as _classify_tempo
|
|
from integrations.tempo import tempo_config_from_env
|
|
from integrations.temporal import classify as _classify_temporal
|
|
from integrations.temporal.client import TemporalConfig
|
|
from integrations.twilio import classify as _classify_twilio
|
|
from integrations.vercel import classify as _classify_vercel
|
|
from integrations.vercel.client import VercelConfig
|
|
from integrations.victoria_logs import classify as _classify_victoria_logs
|
|
from integrations.whatsapp import classify as _classify_whatsapp
|
|
from integrations.x_mcp import build_x_mcp_config
|
|
from integrations.x_mcp import classify as _classify_x_mcp
|
|
from platform.common.coercion import safe_int
|
|
from platform.observability.errors.boundary import report_exception
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _report_env_loader_failure(exc: BaseException, *, integration: str) -> None:
|
|
"""Route a per-vendor env-loader failure to Sentry + warning log.
|
|
|
|
Replaces ``except Exception: pass`` and ``logger.debug(..., exc_info=True)``
|
|
paths in ``load_env_integrations``: integration is still skipped, but the
|
|
misconfiguration reaches Sentry rather than being lost to debug output
|
|
(#1468).
|
|
"""
|
|
report_exception(
|
|
exc,
|
|
logger=logger,
|
|
message=f"env_loader_failed: integration={integration}",
|
|
severity="warning",
|
|
tags={
|
|
"surface": "integration",
|
|
"component": "integrations._catalog_impl",
|
|
"integration": integration,
|
|
"event": "env_loader_failed",
|
|
},
|
|
)
|
|
|
|
|
|
def _should_publish_instance_siblings(instances: object) -> bool:
|
|
"""Return whether an effective integration should expose its ``instances`` list."""
|
|
if not isinstance(instances, list) or not instances:
|
|
return False
|
|
if len(instances) > 1:
|
|
return True
|
|
return str(instances[0].get("name", "default")) != "default"
|
|
|
|
|
|
def _record_instances(record: dict[str, Any]) -> list[dict[str, Any]]:
|
|
"""Normalize a record (v1 or v2 shape) into a list of instance dicts.
|
|
|
|
v2 records return their ``instances`` list directly. v1 records are
|
|
migrated on the fly: ``credentials`` plus every non-structural top-level
|
|
field (e.g. AWS ``role_arn``) become the single ``default`` instance's
|
|
credentials. This matches the v1→v2 store migration so downstream
|
|
classification logic reads ONE uniform shape.
|
|
"""
|
|
if isinstance(record.get("instances"), list):
|
|
return [inst if isinstance(inst, dict) else {} for inst in record["instances"]]
|
|
credentials = dict(record.get("credentials", {}))
|
|
for key, value in record.items():
|
|
if key in _STRUCTURAL_RECORD_FIELDS or key == "credentials":
|
|
continue
|
|
credentials.setdefault(key, value)
|
|
return [{"name": "default", "tags": {}, "credentials": credentials}]
|
|
|
|
|
|
def classify_integrations(integrations: list[dict[str, Any]]) -> dict[str, Any]:
|
|
"""Classify active integrations by service into normalized runtime configs.
|
|
|
|
Backward compat: for each ``service``, ``resolved[service]`` is the flat
|
|
config dict of the DEFAULT (first) instance, matching the pre-multi-instance
|
|
contract. When multiple instances exist (or an instance has an explicit
|
|
non-``default`` name), a sibling key ``_all_{service}_instances`` carries
|
|
all of them as ``[{name, tags, config, integration_id}, ...]``. See
|
|
``integrations/selectors.py`` for consumers.
|
|
"""
|
|
resolved: dict[str, Any] = {}
|
|
all_instances: dict[str, list[dict[str, Any]]] = {}
|
|
|
|
active = [integration for integration in integrations if integration.get("status") == "active"]
|
|
|
|
for integration in active:
|
|
service = str(integration.get("service") or "").strip()
|
|
if not service:
|
|
continue
|
|
|
|
service_lower = service.lower()
|
|
if service_lower in SKIP_CLASSIFIED_SERVICES:
|
|
continue
|
|
|
|
key = service_key(service_lower)
|
|
record_id = str(integration.get("id", "")).strip()
|
|
|
|
for instance in _record_instances(integration):
|
|
credentials = instance.get("credentials", {}) or {}
|
|
instance_name = str(instance.get("name", "default")).strip().lower() or "default"
|
|
instance_tags = instance.get("tags", {}) or {}
|
|
flat_view, flat_key = _classify_service_instance(key, credentials, record_id=record_id)
|
|
if flat_view is None or flat_key is None:
|
|
continue
|
|
resolved.setdefault(flat_key, flat_view)
|
|
# Bucket under the family key so related classifier outputs (e.g.
|
|
# grafana + grafana_local) share one _all_<family>_instances list.
|
|
all_instances.setdefault(family_key(flat_key), []).append(
|
|
{
|
|
"name": instance_name,
|
|
"tags": instance_tags,
|
|
"config": flat_view,
|
|
"integration_id": record_id,
|
|
}
|
|
)
|
|
|
|
for service, instances in all_instances.items():
|
|
if len(instances) > 1 or (instances and instances[0]["name"] != "default"):
|
|
resolved[f"_all_{service}_instances"] = instances
|
|
|
|
resolved["_all"] = active
|
|
return resolved
|
|
|
|
|
|
_ClassifyFn = Callable[[dict[str, Any], str], tuple[Any | None, str | None]]
|
|
|
|
|
|
_CLASSIFIERS: dict[str, _ClassifyFn] = {
|
|
"grafana": _classify_grafana,
|
|
"grafana_local": _classify_grafana,
|
|
"aws": _classify_aws,
|
|
"datadog": _classify_datadog,
|
|
"groundcover": _classify_groundcover,
|
|
"honeycomb": _classify_honeycomb,
|
|
"coralogix": _classify_coralogix,
|
|
"github": _classify_github,
|
|
"sentry": _classify_sentry,
|
|
"gitlab": _classify_gitlab,
|
|
"jenkins": _classify_jenkins,
|
|
"mongodb": _classify_mongodb,
|
|
"redis": _classify_redis,
|
|
"postgresql": _classify_postgresql,
|
|
"mongodb_atlas": _classify_mongodb_atlas,
|
|
"mariadb": _classify_mariadb,
|
|
"vercel": _classify_vercel,
|
|
"opsgenie": _classify_opsgenie,
|
|
"pagerduty": _classify_pagerduty,
|
|
"incident_io": _classify_incident_io,
|
|
"jira": _classify_jira,
|
|
"discord": _classify_discord,
|
|
"telegram": _classify_telegram,
|
|
"whatsapp": _classify_whatsapp,
|
|
"twilio": _classify_twilio,
|
|
"openclaw": _classify_openclaw,
|
|
"posthog_mcp": _classify_posthog_mcp,
|
|
"sentry_mcp": _classify_sentry_mcp,
|
|
"x_mcp": _classify_x_mcp,
|
|
"mysql": _classify_mysql,
|
|
"dagster": _classify_dagster,
|
|
"rabbitmq": _classify_rabbitmq,
|
|
"rds": _classify_rds,
|
|
"airflow": _classify_airflow,
|
|
"betterstack": _classify_betterstack,
|
|
"azure_sql": _classify_azure_sql,
|
|
"alertmanager": _classify_alertmanager,
|
|
"argocd": _classify_argocd,
|
|
"helm": _classify_helm,
|
|
"victoria_logs": _classify_victoria_logs,
|
|
"bitbucket": _classify_bitbucket,
|
|
"snowflake": _classify_snowflake,
|
|
"azure": _classify_azure,
|
|
"openobserve": _classify_openobserve,
|
|
"opensearch": _classify_opensearch,
|
|
"splunk": _classify_splunk,
|
|
"supabase": _classify_supabase,
|
|
"signoz": _classify_signoz,
|
|
"tempo": _classify_tempo,
|
|
"temporal": _classify_temporal,
|
|
"smtp": _classify_smtp,
|
|
"prefect": _classify_prefect,
|
|
}
|
|
|
|
|
|
def _classify_service_instance(
|
|
key: str, credentials: dict[str, Any], *, record_id: str
|
|
) -> tuple[Any | None, str | None]:
|
|
"""Classify one instance into (flat_view, resolved_key).
|
|
|
|
Returns ``(None, None)`` when the instance is invalid or should be skipped
|
|
(e.g. required field missing). The returned ``resolved_key`` is usually
|
|
``key`` itself, but Grafana splits into ``grafana`` or ``grafana_local``
|
|
based on its ``is_local`` property.
|
|
"""
|
|
handler = _CLASSIFIERS.get(key)
|
|
if handler is not None:
|
|
return handler(credentials, record_id)
|
|
# Fallback for unknown services: pass through credentials + record id.
|
|
return {"credentials": credentials, "integration_id": record_id}, key
|
|
|
|
|
|
def _parse_instances_env(env_name: str, service: str) -> dict[str, Any] | None:
|
|
"""Parse ``<SERVICE>_INSTANCES`` env var into a v2 integration record.
|
|
|
|
Accepts a JSON array of instance entries. Each entry may be either
|
|
``{"name": ..., "tags": {...}, "credentials": {...}}`` or a flat
|
|
``{"name": ..., "tags": {...}, <field>: <value>, ...}`` — we accept
|
|
both shapes and normalize to ``credentials``. Returns None if the env
|
|
var is unset, empty, invalid JSON, or not a non-empty list (logs a
|
|
warning on parse failure so callers can fall through to legacy vars).
|
|
|
|
Critical: always returns a SINGLE record with multiple instances inside,
|
|
never multiple records — otherwise ``merge_integrations_by_service``
|
|
would drop all but one (PR #527 bug #2).
|
|
"""
|
|
raw = os.getenv(env_name, "").strip()
|
|
if not raw:
|
|
return None
|
|
try:
|
|
parsed = json.loads(raw)
|
|
except json.JSONDecodeError as exc:
|
|
# Do NOT include exc.msg or the raw value — JSONDecodeError messages
|
|
# embed a slice of the offending input, which could leak a fragment
|
|
# of an API key if the env var was accidentally populated with a
|
|
# credential instead of a JSON array. Log only position + line/col.
|
|
logger.warning(
|
|
"%s is not valid JSON (parse failed at line %d col %d); falling back to legacy vars",
|
|
env_name,
|
|
exc.lineno,
|
|
exc.colno,
|
|
)
|
|
return None
|
|
if not isinstance(parsed, list) or not parsed:
|
|
return None
|
|
instances: list[dict[str, Any]] = []
|
|
for entry in parsed:
|
|
if not isinstance(entry, dict):
|
|
continue
|
|
nested_creds = entry.get("credentials")
|
|
if isinstance(nested_creds, dict):
|
|
credentials = dict(nested_creds)
|
|
else:
|
|
credentials = {k: v for k, v in entry.items() if k not in {"name", "tags"}}
|
|
name = str(entry.get("name", "default")).strip().lower() or "default"
|
|
tags = entry.get("tags") if isinstance(entry.get("tags"), dict) else {}
|
|
instances.append({"name": name, "tags": tags, "credentials": credentials})
|
|
if not instances:
|
|
return None
|
|
return {
|
|
"id": f"env-{service}",
|
|
"service": service,
|
|
"status": "active",
|
|
"instances": instances,
|
|
}
|
|
|
|
|
|
def _active_env_record(
|
|
service: str,
|
|
credentials: dict[str, Any],
|
|
*,
|
|
record_id: str | None = None,
|
|
**extra: Any,
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"id": record_id or f"env-{service.replace('_', '-')}",
|
|
"service": service,
|
|
"status": "active",
|
|
**extra,
|
|
"credentials": credentials,
|
|
}
|
|
|
|
|
|
def load_env_integrations() -> list[dict[str, Any]]:
|
|
"""Build integration records from local environment variables."""
|
|
integrations: list[dict[str, Any]] = []
|
|
|
|
grafana_multi = _parse_instances_env("GRAFANA_INSTANCES", "grafana")
|
|
if grafana_multi is not None:
|
|
integrations.append(grafana_multi)
|
|
grafana_endpoint = ""
|
|
grafana_api_key = ""
|
|
else:
|
|
grafana_endpoint = os.getenv("GRAFANA_INSTANCE_URL", "").strip()
|
|
grafana_api_key = os.getenv("GRAFANA_READ_TOKEN", "").strip()
|
|
if grafana_endpoint and grafana_api_key:
|
|
try:
|
|
grafana_config = GrafanaIntegrationConfig.model_validate(
|
|
{
|
|
"endpoint": grafana_endpoint,
|
|
"api_key": grafana_api_key,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="grafana")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"grafana",
|
|
{
|
|
"endpoint": grafana_config.endpoint,
|
|
"api_key": grafana_config.api_key,
|
|
},
|
|
)
|
|
)
|
|
|
|
datadog_multi = _parse_instances_env("DD_INSTANCES", "datadog")
|
|
if datadog_multi is not None:
|
|
integrations.append(datadog_multi)
|
|
datadog_api_key = ""
|
|
datadog_app_key = ""
|
|
datadog_site = ""
|
|
else:
|
|
datadog_api_key = os.getenv("DD_API_KEY", "").strip()
|
|
datadog_app_key = os.getenv("DD_APP_KEY", "").strip()
|
|
datadog_site = os.getenv("DD_SITE", "datadoghq.com").strip() or "datadoghq.com"
|
|
if datadog_api_key and datadog_app_key:
|
|
try:
|
|
datadog_config = DatadogIntegrationConfig.model_validate(
|
|
{
|
|
"api_key": datadog_api_key,
|
|
"app_key": datadog_app_key,
|
|
"site": datadog_site,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="datadog")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"datadog",
|
|
datadog_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
groundcover_multi = _parse_instances_env("GROUNDCOVER_INSTANCES", "groundcover")
|
|
if groundcover_multi is not None:
|
|
integrations.append(groundcover_multi)
|
|
groundcover_api_key = ""
|
|
else:
|
|
groundcover_api_key = (
|
|
os.getenv("GROUNDCOVER_API_KEY", "").strip()
|
|
or os.getenv("GROUNDCOVER_MCP_TOKEN", "").strip()
|
|
)
|
|
if groundcover_api_key:
|
|
# The groundcover config validates the MCP URL (HTTPS-or-loopback), which
|
|
# can raise on a bad GROUNDCOVER_MCP_URL. Guard it so one malformed value
|
|
# cannot abort discovery of every other env integration.
|
|
try:
|
|
groundcover_config = GroundcoverIntegrationConfig.model_validate(
|
|
{
|
|
"api_key": groundcover_api_key,
|
|
"mcp_url": os.getenv("GROUNDCOVER_MCP_URL", "").strip(),
|
|
"tenant_uuid": os.getenv("GROUNDCOVER_TENANT_UUID", "").strip(),
|
|
"backend_id": os.getenv("GROUNDCOVER_BACKEND_ID", "").strip(),
|
|
"timezone": os.getenv("GROUNDCOVER_TIMEZONE", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="groundcover")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"groundcover",
|
|
groundcover_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
honeycomb_multi = _parse_instances_env("HONEYCOMB_INSTANCES", "honeycomb")
|
|
if honeycomb_multi is not None:
|
|
integrations.append(honeycomb_multi)
|
|
honeycomb_api_key = ""
|
|
else:
|
|
honeycomb_api_key = os.getenv("HONEYCOMB_API_KEY", "").strip()
|
|
if honeycomb_api_key:
|
|
try:
|
|
honeycomb_config = HoneycombIntegrationConfig.model_validate(
|
|
{
|
|
"api_key": honeycomb_api_key,
|
|
"dataset": os.getenv("HONEYCOMB_DATASET", "").strip(),
|
|
"base_url": os.getenv("HONEYCOMB_API_URL", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="honeycomb")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"honeycomb",
|
|
honeycomb_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
coralogix_multi = _parse_instances_env("CORALOGIX_INSTANCES", "coralogix")
|
|
if coralogix_multi is not None:
|
|
integrations.append(coralogix_multi)
|
|
coralogix_api_key = ""
|
|
else:
|
|
coralogix_api_key = os.getenv("CORALOGIX_API_KEY", "").strip()
|
|
if coralogix_api_key:
|
|
try:
|
|
coralogix_config = CoralogixIntegrationConfig.model_validate(
|
|
{
|
|
"api_key": coralogix_api_key,
|
|
"base_url": os.getenv("CORALOGIX_API_URL", "").strip(),
|
|
"application_name": os.getenv("CORALOGIX_APPLICATION_NAME", "").strip(),
|
|
"subsystem_name": os.getenv("CORALOGIX_SUBSYSTEM_NAME", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="coralogix")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"coralogix",
|
|
coralogix_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
aws_multi = _parse_instances_env("AWS_INSTANCES", "aws")
|
|
if aws_multi is not None:
|
|
integrations.append(aws_multi)
|
|
aws_role_arn = ""
|
|
aws_external_id = ""
|
|
aws_region = "us-east-1"
|
|
aws_access_key_id = ""
|
|
aws_secret_access_key = ""
|
|
aws_session_token = ""
|
|
else:
|
|
aws_role_arn = os.getenv("AWS_ROLE_ARN", "").strip()
|
|
aws_external_id = os.getenv("AWS_EXTERNAL_ID", "").strip()
|
|
aws_region = os.getenv("AWS_REGION", "us-east-1").strip() or "us-east-1"
|
|
aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID", "").strip()
|
|
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY", "").strip()
|
|
aws_session_token = os.getenv("AWS_SESSION_TOKEN", "").strip()
|
|
if aws_role_arn:
|
|
try:
|
|
aws_config = AWSIntegrationConfig.model_validate(
|
|
{
|
|
"role_arn": aws_role_arn,
|
|
"external_id": aws_external_id,
|
|
"region": aws_region,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="aws")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"aws",
|
|
{"region": aws_config.region},
|
|
role_arn=aws_config.role_arn,
|
|
external_id=aws_config.external_id,
|
|
)
|
|
)
|
|
elif aws_access_key_id and aws_secret_access_key:
|
|
try:
|
|
aws_config = AWSIntegrationConfig.model_validate(
|
|
{
|
|
"region": aws_region,
|
|
"credentials": {
|
|
"access_key_id": aws_access_key_id,
|
|
"secret_access_key": aws_secret_access_key,
|
|
"session_token": aws_session_token,
|
|
},
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="aws")
|
|
else:
|
|
aws_credentials = aws_config.credentials
|
|
if aws_credentials is not None:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"aws",
|
|
{
|
|
"access_key_id": aws_credentials.access_key_id,
|
|
"secret_access_key": aws_credentials.secret_access_key,
|
|
"session_token": aws_credentials.session_token,
|
|
"region": aws_config.region,
|
|
},
|
|
)
|
|
)
|
|
|
|
github_mode = os.getenv("GITHUB_MCP_MODE", "streamable-http").strip() or "streamable-http"
|
|
github_url = os.getenv("GITHUB_MCP_URL", "").strip()
|
|
github_command = os.getenv("GITHUB_MCP_COMMAND", "").strip()
|
|
github_args = os.getenv("GITHUB_MCP_ARGS", "").strip()
|
|
github_auth_token = os.getenv("GITHUB_MCP_AUTH_TOKEN", "").strip()
|
|
github_toolsets = os.getenv("GITHUB_MCP_TOOLSETS", "").strip()
|
|
if (github_mode == "stdio" and github_command) or (github_mode != "stdio" and github_url):
|
|
github_config = build_github_mcp_config(
|
|
{
|
|
"url": github_url,
|
|
"mode": github_mode,
|
|
"command": github_command,
|
|
"args": [part for part in github_args.split() if part],
|
|
"auth_token": github_auth_token,
|
|
"toolsets": [part.strip() for part in github_toolsets.split(",") if part.strip()],
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"github",
|
|
github_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
sentry_org_slug = os.getenv("SENTRY_ORG_SLUG", "").strip()
|
|
sentry_auth_token = os.getenv("SENTRY_AUTH_TOKEN", "").strip()
|
|
if sentry_org_slug and sentry_auth_token:
|
|
sentry_config = build_sentry_config(
|
|
{
|
|
"base_url": os.getenv("SENTRY_URL", "https://sentry.io").strip()
|
|
or "https://sentry.io",
|
|
"organization_slug": sentry_org_slug,
|
|
"auth_token": sentry_auth_token,
|
|
"project_slug": os.getenv("SENTRY_PROJECT_SLUG", "").strip(),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"sentry",
|
|
sentry_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
gitlab_access_token = resolve_env_credential("GITLAB_ACCESS_TOKEN")
|
|
if gitlab_access_token:
|
|
gitlab_config = build_gitlab_config(
|
|
{
|
|
"base_url": os.getenv("GITLAB_BASE_URL", DEFAULT_GITLAB_BASE_URL).strip()
|
|
or DEFAULT_GITLAB_BASE_URL,
|
|
"auth_token": gitlab_access_token,
|
|
}
|
|
)
|
|
integrations.append(_active_env_record("gitlab", gitlab_config.model_dump()))
|
|
|
|
mongodb_connection_string = os.getenv("MONGODB_CONNECTION_STRING", "").strip()
|
|
if mongodb_connection_string:
|
|
mongodb_config = build_mongodb_config(
|
|
{
|
|
"connection_string": mongodb_connection_string,
|
|
"database": os.getenv("MONGODB_DATABASE", "").strip(),
|
|
"auth_source": os.getenv("MONGODB_AUTH_SOURCE", "admin").strip() or "admin",
|
|
"tls": os.getenv("MONGODB_TLS", "true").strip().lower() in ("true", "1", "yes"),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"mongodb",
|
|
mongodb_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
redis_config = redis_config_from_env()
|
|
if redis_config:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"redis",
|
|
redis_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
postgresql_host = os.getenv("POSTGRESQL_HOST", "").strip()
|
|
postgresql_database = os.getenv("POSTGRESQL_DATABASE", "").strip()
|
|
if postgresql_host and postgresql_database:
|
|
postgresql_config = build_postgresql_config(
|
|
{
|
|
"host": postgresql_host,
|
|
"port": int(_pg_port)
|
|
if (_pg_port := os.getenv("POSTGRESQL_PORT", "").strip()) and _pg_port.isdigit()
|
|
else 5432,
|
|
"database": postgresql_database,
|
|
"username": os.getenv("POSTGRESQL_USERNAME", "postgres").strip() or "postgres",
|
|
"password": os.getenv("POSTGRESQL_PASSWORD", "").strip(),
|
|
"ssl_mode": os.getenv("POSTGRESQL_SSL_MODE", "prefer").strip() or "prefer",
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"postgresql",
|
|
postgresql_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
argocd_multi = _parse_instances_env("ARGOCD_INSTANCES", "argocd")
|
|
if argocd_multi is not None:
|
|
integrations.append(argocd_multi)
|
|
argocd_base_url = ""
|
|
argocd_auth_token = ""
|
|
argocd_username = ""
|
|
argocd_password = ""
|
|
else:
|
|
argocd_base_url = os.getenv("ARGOCD_BASE_URL", "").strip()
|
|
argocd_auth_token = os.getenv("ARGOCD_AUTH_TOKEN", os.getenv("ARGOCD_TOKEN", "")).strip()
|
|
argocd_username = os.getenv("ARGOCD_USERNAME", "").strip()
|
|
argocd_password = os.getenv("ARGOCD_PASSWORD", "").strip()
|
|
if argocd_base_url and (argocd_auth_token or (argocd_username and argocd_password)):
|
|
try:
|
|
argocd_config = ArgoCDIntegrationConfig.model_validate(
|
|
{
|
|
"base_url": argocd_base_url,
|
|
"bearer_token": argocd_auth_token,
|
|
"username": argocd_username,
|
|
"password": argocd_password,
|
|
"project": os.getenv("ARGOCD_PROJECT", "").strip(),
|
|
"app_namespace": os.getenv("ARGOCD_APP_NAMESPACE", "").strip(),
|
|
"verify_ssl": os.getenv("ARGOCD_VERIFY_SSL", "true").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
# Invalid env-derived config: skip ArgoCD entry rather than fail
|
|
# discovery, but report so operators can see the misconfig.
|
|
_report_env_loader_failure(exc, integration="argocd")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"argocd",
|
|
argocd_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
helm_env_enabled = os.getenv("OSRE_HELM_INTEGRATION", "").strip().lower() in {
|
|
"1",
|
|
"true",
|
|
"yes",
|
|
}
|
|
if helm_env_enabled:
|
|
try:
|
|
helm_env_config = HelmIntegrationConfig.model_validate(
|
|
{
|
|
"helm_path": os.getenv("HELM_PATH", "helm").strip() or "helm",
|
|
"kube_context": os.getenv("HELM_KUBE_CONTEXT", "").strip(),
|
|
"kubeconfig": os.getenv("HELM_KUBECONFIG", "").strip(),
|
|
"default_namespace": os.getenv("HELM_NAMESPACE", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="helm")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"helm",
|
|
helm_env_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
vercel_api_token = os.getenv("VERCEL_API_TOKEN", "").strip()
|
|
if vercel_api_token:
|
|
try:
|
|
vercel_config = VercelConfig.model_validate(
|
|
{
|
|
"api_token": vercel_api_token,
|
|
"team_id": os.getenv("VERCEL_TEAM_ID", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="vercel")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"vercel",
|
|
vercel_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
opsgenie_api_key = os.getenv("OPSGENIE_API_KEY", "").strip()
|
|
if opsgenie_api_key:
|
|
try:
|
|
opsgenie_config = OpsGenieIntegrationConfig.model_validate(
|
|
{
|
|
"api_key": opsgenie_api_key,
|
|
"region": os.getenv("OPSGENIE_REGION", "us").strip() or "us",
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="opsgenie")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"opsgenie",
|
|
opsgenie_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
pagerduty_api_key = os.getenv("PAGERDUTY_API_KEY", "").strip()
|
|
if pagerduty_api_key:
|
|
try:
|
|
_envs: dict[str, Any] = {"api_key": pagerduty_api_key}
|
|
base_url = os.getenv("PAGERDUTY_BASE_URL", "").strip()
|
|
if base_url:
|
|
_envs["base_url"] = base_url
|
|
pagerduty_config = PagerDutyIntegrationConfig.model_validate(_envs)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="pagerduty")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"pagerduty",
|
|
pagerduty_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
incident_io_api_key = resolve_env_credential("INCIDENT_IO_API_KEY")
|
|
if incident_io_api_key:
|
|
try:
|
|
incident_io_config = IncidentIoIntegrationConfig.model_validate(
|
|
{
|
|
"api_key": incident_io_api_key,
|
|
"base_url": os.getenv("INCIDENT_IO_BASE_URL", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="incident_io")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"incident_io",
|
|
incident_io_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
jira_base_url = os.getenv("JIRA_BASE_URL", "").strip()
|
|
jira_email = os.getenv("JIRA_EMAIL", "").strip()
|
|
jira_api_token = os.getenv("JIRA_API_TOKEN", "").strip()
|
|
jira_project_key = os.getenv("JIRA_PROJECT_KEY", "").strip()
|
|
if jira_base_url and jira_email and jira_api_token:
|
|
try:
|
|
jira_config = JiraIntegrationConfig.model_validate(
|
|
{
|
|
"base_url": jira_base_url,
|
|
"email": jira_email,
|
|
"api_token": jira_api_token,
|
|
"project_key": jira_project_key,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="jira")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"jira",
|
|
jira_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
discord_bot_token = resolve_env_credential("DISCORD_BOT_TOKEN")
|
|
if discord_bot_token:
|
|
try:
|
|
discord_config = DiscordBotConfig.model_validate(
|
|
{
|
|
"bot_token": discord_bot_token,
|
|
"application_id": os.getenv("DISCORD_APPLICATION_ID", "").strip(),
|
|
"public_key": os.getenv("DISCORD_PUBLIC_KEY", "").strip(),
|
|
"default_channel_id": os.getenv("DISCORD_DEFAULT_CHANNEL_ID", "").strip()
|
|
or None,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="discord")
|
|
else:
|
|
integrations.append(_active_env_record("discord", discord_config.model_dump()))
|
|
|
|
airflow_config = airflow_config_from_env()
|
|
if airflow_config is not None:
|
|
integrations.append(_active_env_record("airflow", airflow_config.model_dump()))
|
|
|
|
telegram_bot_token = os.getenv("TELEGRAM_BOT_TOKEN", "").strip()
|
|
if telegram_bot_token:
|
|
try:
|
|
tg_config = TelegramBotConfig.model_validate(
|
|
{
|
|
"bot_token": telegram_bot_token,
|
|
"default_chat_id": os.getenv("TELEGRAM_DEFAULT_CHAT_ID", "").strip() or None,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="telegram")
|
|
else:
|
|
integrations.append(_active_env_record("telegram", tg_config.model_dump()))
|
|
|
|
smtp_host = os.getenv("SMTP_HOST", "").strip()
|
|
if smtp_host:
|
|
try:
|
|
smtp_config = SMTPIntegrationConfig.model_validate(
|
|
{
|
|
"host": smtp_host,
|
|
"port": os.getenv("SMTP_PORT", "").strip() or 587,
|
|
"security": os.getenv("SMTP_SECURITY", "").strip() or "starttls",
|
|
"username": os.getenv("SMTP_USERNAME", "").strip(),
|
|
"password": resolve_env_credential("SMTP_PASSWORD"),
|
|
"from_address": os.getenv("SMTP_FROM_ADDRESS", "").strip(),
|
|
"default_to": os.getenv("SMTP_DEFAULT_TO", "").strip() or None,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="smtp")
|
|
else:
|
|
integrations.append(_active_env_record("smtp", smtp_config.model_dump()))
|
|
|
|
# Shared Twilio account credentials — consumed by both the WhatsApp and
|
|
# the SMS env-bootstrap blocks below.
|
|
twilio_account_sid = os.getenv("TWILIO_ACCOUNT_SID", "").strip()
|
|
twilio_auth_token = os.getenv("TWILIO_AUTH_TOKEN", "").strip()
|
|
|
|
whatsapp_from_number = os.getenv("TWILIO_WHATSAPP_FROM", "").strip()
|
|
if twilio_account_sid and twilio_auth_token and whatsapp_from_number:
|
|
try:
|
|
wa_config = WhatsAppConfig.model_validate(
|
|
{
|
|
"account_sid": twilio_account_sid,
|
|
"auth_token": twilio_auth_token,
|
|
"from_number": whatsapp_from_number,
|
|
"default_to": os.getenv("WHATSAPP_DEFAULT_TO", "").strip() or None,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="whatsapp")
|
|
else:
|
|
integrations.append(_active_env_record("whatsapp", wa_config.model_dump()))
|
|
|
|
# Twilio SMS integration — independent of the legacy WhatsApp record.
|
|
# Hydrated when account+token are present AND an SMS sender is set
|
|
# (a from_number or a Messaging Service SID).
|
|
twilio_sms_from = os.getenv("TWILIO_SMS_FROM", "").strip()
|
|
twilio_sms_messaging_service = os.getenv("TWILIO_SMS_MESSAGING_SERVICE_SID", "").strip()
|
|
if (
|
|
twilio_account_sid
|
|
and twilio_auth_token
|
|
and (twilio_sms_from or twilio_sms_messaging_service)
|
|
):
|
|
twilio_payload: dict[str, Any] = {
|
|
"account_sid": twilio_account_sid,
|
|
"auth_token": twilio_auth_token,
|
|
"sms": {
|
|
"enabled": True,
|
|
"from_number": twilio_sms_from,
|
|
"messaging_service_sid": twilio_sms_messaging_service,
|
|
"default_to": os.getenv("TWILIO_SMS_DEFAULT_TO", "").strip() or None,
|
|
},
|
|
}
|
|
try:
|
|
twilio_config = TwilioIntegrationConfig.model_validate(twilio_payload)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="twilio")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"twilio",
|
|
twilio_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
atlas_pub = os.getenv("MONGODB_ATLAS_PUBLIC_KEY", "").strip()
|
|
atlas_priv = os.getenv("MONGODB_ATLAS_PRIVATE_KEY", "").strip()
|
|
atlas_project = os.getenv("MONGODB_ATLAS_PROJECT_ID", "").strip()
|
|
if atlas_pub and atlas_priv and atlas_project:
|
|
try:
|
|
atlas_config = build_mongodb_atlas_config(
|
|
{
|
|
"api_public_key": atlas_pub,
|
|
"api_private_key": atlas_priv,
|
|
"project_id": atlas_project,
|
|
"base_url": os.getenv(
|
|
"MONGODB_ATLAS_BASE_URL", "https://cloud.mongodb.com/api/atlas/v2"
|
|
).strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="mongodb_atlas")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"mongodb_atlas",
|
|
atlas_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
openclaw_url = os.getenv("OPENCLAW_MCP_URL", "").strip()
|
|
openclaw_command = os.getenv("OPENCLAW_MCP_COMMAND", "").strip()
|
|
openclaw_mode = os.getenv("OPENCLAW_MCP_MODE", "streamable-http").strip().lower()
|
|
openclaw_mode = openclaw_mode or "streamable-http"
|
|
if (openclaw_mode == "stdio" and openclaw_command) or (
|
|
openclaw_mode != "stdio" and openclaw_url
|
|
):
|
|
try:
|
|
openclaw_config = build_openclaw_config(
|
|
{
|
|
"url": openclaw_url,
|
|
"mode": openclaw_mode,
|
|
"command": openclaw_command,
|
|
"args": [
|
|
part for part in os.getenv("OPENCLAW_MCP_ARGS", "").strip().split() if part
|
|
],
|
|
"auth_token": resolve_env_credential("OPENCLAW_MCP_AUTH_TOKEN"),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"openclaw",
|
|
{
|
|
**openclaw_config.model_dump(exclude={"integration_id"}),
|
|
"connection_verified": True,
|
|
},
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="openclaw")
|
|
|
|
posthog_mcp_mode = os.getenv("POSTHOG_MCP_MODE", "streamable-http").strip().lower()
|
|
posthog_mcp_mode = posthog_mcp_mode or "streamable-http"
|
|
posthog_mcp_command = os.getenv("POSTHOG_MCP_COMMAND", "").strip()
|
|
posthog_mcp_token = resolve_env_credential("POSTHOG_MCP_AUTH_TOKEN")
|
|
posthog_mcp_url = os.getenv("POSTHOG_MCP_URL", "").strip()
|
|
if posthog_mcp_mode != "stdio" and posthog_mcp_token and not posthog_mcp_url:
|
|
posthog_mcp_url = DEFAULT_POSTHOG_MCP_URL
|
|
if (posthog_mcp_mode == "stdio" and posthog_mcp_command) or (
|
|
posthog_mcp_mode != "stdio" and posthog_mcp_url and posthog_mcp_token
|
|
):
|
|
read_only_env = os.getenv("POSTHOG_MCP_READ_ONLY", "").strip().lower()
|
|
read_only = read_only_env not in ("false", "0", "no") if read_only_env else True
|
|
try:
|
|
posthog_mcp_config = build_posthog_mcp_config(
|
|
{
|
|
"url": posthog_mcp_url,
|
|
"mode": posthog_mcp_mode,
|
|
"command": posthog_mcp_command,
|
|
"args": [
|
|
part for part in os.getenv("POSTHOG_MCP_ARGS", "").strip().split() if part
|
|
],
|
|
"auth_token": posthog_mcp_token,
|
|
"organization_id": os.getenv("POSTHOG_MCP_ORGANIZATION_ID", "").strip(),
|
|
"project_id": os.getenv("POSTHOG_MCP_PROJECT_ID", "").strip(),
|
|
"features": os.getenv("POSTHOG_MCP_FEATURES", "").strip(),
|
|
"read_only": read_only,
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"posthog_mcp",
|
|
{
|
|
**posthog_mcp_config.model_dump(exclude={"integration_id"}),
|
|
"connection_verified": True,
|
|
},
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="posthog_mcp")
|
|
|
|
sentry_mcp_mode = os.getenv("SENTRY_MCP_MODE", "streamable-http").strip().lower()
|
|
sentry_mcp_mode = sentry_mcp_mode or "streamable-http"
|
|
sentry_mcp_command = os.getenv("SENTRY_MCP_COMMAND", "").strip()
|
|
sentry_mcp_token = resolve_env_credential("SENTRY_MCP_AUTH_TOKEN")
|
|
sentry_mcp_url = os.getenv("SENTRY_MCP_URL", "").strip()
|
|
if sentry_mcp_mode != "stdio" and sentry_mcp_token and not sentry_mcp_url:
|
|
sentry_mcp_url = DEFAULT_SENTRY_MCP_URL
|
|
if (sentry_mcp_mode == "stdio" and sentry_mcp_command) or (
|
|
sentry_mcp_mode != "stdio" and sentry_mcp_url and sentry_mcp_token
|
|
):
|
|
try:
|
|
sentry_mcp_config = build_sentry_mcp_config(
|
|
{
|
|
"url": sentry_mcp_url,
|
|
"mode": sentry_mcp_mode,
|
|
"command": sentry_mcp_command,
|
|
"args": [
|
|
part for part in os.getenv("SENTRY_MCP_ARGS", "").strip().split() if part
|
|
],
|
|
"auth_token": sentry_mcp_token,
|
|
"host": os.getenv("SENTRY_MCP_HOST", "").strip(),
|
|
"organization_slug": os.getenv("SENTRY_MCP_ORGANIZATION_SLUG", "").strip(),
|
|
"project_slug": os.getenv("SENTRY_MCP_PROJECT_SLUG", "").strip(),
|
|
"skills": os.getenv("SENTRY_MCP_SKILLS", "").strip(),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"sentry_mcp",
|
|
{
|
|
**sentry_mcp_config.model_dump(exclude={"integration_id"}),
|
|
"connection_verified": True,
|
|
},
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="sentry_mcp")
|
|
|
|
x_mcp_mode = os.getenv("X_MCP_MODE", "streamable-http").strip().lower()
|
|
x_mcp_mode = x_mcp_mode or "streamable-http"
|
|
x_mcp_command = os.getenv("X_MCP_COMMAND", "").strip()
|
|
x_mcp_token = resolve_env_credential("X_MCP_AUTH_TOKEN")
|
|
x_mcp_bearer_token = resolve_env_credential("X_BEARER_TOKEN")
|
|
x_mcp_url = os.getenv("X_MCP_URL", "").strip()
|
|
if (x_mcp_mode == "stdio" and x_mcp_command) or (x_mcp_mode != "stdio" and x_mcp_url):
|
|
try:
|
|
x_mcp_config = build_x_mcp_config(
|
|
{
|
|
"url": x_mcp_url,
|
|
"mode": x_mcp_mode,
|
|
"command": x_mcp_command,
|
|
"args": [part for part in os.getenv("X_MCP_ARGS", "").strip().split() if part],
|
|
"auth_token": x_mcp_token,
|
|
"bearer_token": x_mcp_bearer_token,
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"x_mcp",
|
|
{
|
|
**x_mcp_config.model_dump(exclude={"integration_id"}),
|
|
"connection_verified": True,
|
|
},
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="x_mcp")
|
|
|
|
mariadb_host = os.getenv("MARIADB_HOST", "").strip()
|
|
mariadb_database = os.getenv("MARIADB_DATABASE", "").strip()
|
|
if mariadb_host and mariadb_database:
|
|
try:
|
|
mariadb_config = build_mariadb_config(
|
|
{
|
|
"host": mariadb_host,
|
|
"port": os.getenv("MARIADB_PORT", "3306").strip(),
|
|
"database": mariadb_database,
|
|
"username": os.getenv("MARIADB_USERNAME", "").strip(),
|
|
"password": os.getenv("MARIADB_PASSWORD", "").strip(),
|
|
"ssl": os.getenv("MARIADB_SSL", "true").strip().lower() in ("true", "1", "yes"),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"mariadb",
|
|
mariadb_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="mariadb")
|
|
|
|
dagster_endpoint = os.getenv("DAGSTER_ENDPOINT", "").strip()
|
|
if dagster_endpoint:
|
|
try:
|
|
dagster_config = build_dagster_config(
|
|
{
|
|
"endpoint": dagster_endpoint,
|
|
"api_token": os.getenv("DAGSTER_API_TOKEN", "").strip(),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"dagster",
|
|
dagster_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="dagster")
|
|
|
|
rabbitmq_host = os.getenv("RABBITMQ_HOST", "").strip()
|
|
rabbitmq_username = os.getenv("RABBITMQ_USERNAME", "").strip()
|
|
if rabbitmq_host and rabbitmq_username:
|
|
try:
|
|
rabbitmq_config = build_rabbitmq_config(
|
|
{
|
|
"host": rabbitmq_host,
|
|
"management_port": os.getenv("RABBITMQ_MANAGEMENT_PORT", "15672").strip(),
|
|
"username": rabbitmq_username,
|
|
"password": os.getenv("RABBITMQ_PASSWORD", ""),
|
|
"vhost": os.getenv("RABBITMQ_VHOST", "/").strip(),
|
|
"ssl": os.getenv("RABBITMQ_SSL", "false").strip().lower()
|
|
in ("true", "1", "yes"),
|
|
"verify_ssl": os.getenv("RABBITMQ_VERIFY_SSL", "true").strip().lower()
|
|
in ("true", "1", "yes"),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"rabbitmq",
|
|
rabbitmq_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="rabbitmq")
|
|
|
|
try:
|
|
rds_config = rds_config_from_env()
|
|
except Exception as exc:
|
|
rds_config = None
|
|
_report_env_loader_failure(exc, integration="rds")
|
|
if rds_config is not None and rds_config.is_configured:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"rds",
|
|
rds_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
bs_endpoint = os.getenv("BETTERSTACK_QUERY_ENDPOINT", "").strip()
|
|
bs_username = os.getenv("BETTERSTACK_USERNAME", "").strip()
|
|
if bs_endpoint and bs_username:
|
|
try:
|
|
bs_config = build_betterstack_config(
|
|
{
|
|
"query_endpoint": bs_endpoint,
|
|
"username": bs_username,
|
|
"password": os.getenv("BETTERSTACK_PASSWORD", ""),
|
|
"sources": os.getenv("BETTERSTACK_SOURCES", ""),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"betterstack",
|
|
bs_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="betterstack")
|
|
|
|
mysql_host = os.getenv("MYSQL_HOST", "").strip()
|
|
mysql_database = os.getenv("MYSQL_DATABASE", "").strip()
|
|
if mysql_host and mysql_database:
|
|
mysql_config = build_mysql_config(
|
|
{
|
|
"host": mysql_host,
|
|
"port": int(_mysql_port)
|
|
if (_mysql_port := os.getenv("MYSQL_PORT", "").strip()) and _mysql_port.isdigit()
|
|
else 3306,
|
|
"database": mysql_database,
|
|
"username": os.getenv("MYSQL_USERNAME", "root").strip() or "root",
|
|
"password": os.getenv("MYSQL_PASSWORD", "").strip(),
|
|
"ssl_mode": os.getenv("MYSQL_SSL_MODE", "preferred").strip() or "preferred",
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"mysql",
|
|
mysql_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
azure_sql_server = os.getenv("AZURE_SQL_SERVER", "").strip()
|
|
azure_sql_database = os.getenv("AZURE_SQL_DATABASE", "").strip()
|
|
if azure_sql_server and azure_sql_database:
|
|
_az_port = os.getenv("AZURE_SQL_PORT", "").strip()
|
|
azure_sql_config = build_azure_sql_config(
|
|
{
|
|
"server": azure_sql_server,
|
|
"port": int(_az_port) if _az_port and _az_port.isdigit() else 1433,
|
|
"database": azure_sql_database,
|
|
"username": os.getenv("AZURE_SQL_USERNAME", "").strip(),
|
|
"password": os.getenv("AZURE_SQL_PASSWORD", "").strip(),
|
|
"driver": os.getenv("AZURE_SQL_DRIVER", "ODBC Driver 18 for SQL Server").strip(),
|
|
"encrypt": os.getenv("AZURE_SQL_ENCRYPT", "true").strip().lower()
|
|
in ("true", "1", "yes"),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"azure_sql",
|
|
azure_sql_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
bitbucket_workspace = os.getenv("BITBUCKET_WORKSPACE", "").strip()
|
|
if bitbucket_workspace:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"bitbucket",
|
|
{
|
|
"workspace": bitbucket_workspace,
|
|
"username": os.getenv("BITBUCKET_USERNAME", "").strip(),
|
|
"app_password": os.getenv("BITBUCKET_APP_PASSWORD", "").strip(),
|
|
"base_url": os.getenv(
|
|
"BITBUCKET_BASE_URL", "https://api.bitbucket.org/2.0"
|
|
).strip()
|
|
or "https://api.bitbucket.org/2.0",
|
|
"max_results": safe_int(os.getenv("BITBUCKET_MAX_RESULTS", "25"), 25),
|
|
},
|
|
)
|
|
)
|
|
|
|
snowflake_account = (
|
|
os.getenv("SNOWFLAKE_ACCOUNT_IDENTIFIER", "").strip()
|
|
or os.getenv("SNOWFLAKE_ACCOUNT", "").strip()
|
|
)
|
|
snowflake_token = os.getenv("SNOWFLAKE_TOKEN", "").strip()
|
|
if snowflake_account and snowflake_token:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"snowflake",
|
|
{
|
|
"account_identifier": snowflake_account,
|
|
"user": os.getenv("SNOWFLAKE_USER", "").strip(),
|
|
"password": os.getenv("SNOWFLAKE_PASSWORD", "").strip(),
|
|
"token": snowflake_token,
|
|
"warehouse": os.getenv("SNOWFLAKE_WAREHOUSE", "").strip(),
|
|
"role": os.getenv("SNOWFLAKE_ROLE", "").strip(),
|
|
"database": os.getenv("SNOWFLAKE_DATABASE", "").strip(),
|
|
"schema": os.getenv("SNOWFLAKE_SCHEMA", "").strip(),
|
|
"max_results": safe_int(os.getenv("SNOWFLAKE_MAX_RESULTS", "50"), 50),
|
|
},
|
|
)
|
|
)
|
|
|
|
azure_workspace_id = os.getenv("AZURE_LOG_ANALYTICS_WORKSPACE_ID", "").strip()
|
|
azure_access_token = os.getenv("AZURE_LOG_ANALYTICS_TOKEN", "").strip()
|
|
if azure_workspace_id and azure_access_token:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"azure",
|
|
{
|
|
"workspace_id": azure_workspace_id,
|
|
"access_token": azure_access_token,
|
|
"endpoint": (
|
|
os.getenv(
|
|
"AZURE_LOG_ANALYTICS_ENDPOINT", "https://api.loganalytics.io"
|
|
).strip()
|
|
or "https://api.loganalytics.io"
|
|
),
|
|
"tenant_id": os.getenv("AZURE_TENANT_ID", "").strip(),
|
|
"subscription_id": os.getenv("AZURE_SUBSCRIPTION_ID", "").strip(),
|
|
"max_results": safe_int(os.getenv("AZURE_MAX_RESULTS", "100"), 100),
|
|
},
|
|
)
|
|
)
|
|
|
|
openobserve_url = os.getenv("OPENOBSERVE_URL", "").strip()
|
|
openobserve_token = os.getenv("OPENOBSERVE_TOKEN", "").strip()
|
|
openobserve_username = os.getenv("OPENOBSERVE_USERNAME", "").strip()
|
|
openobserve_password = os.getenv("OPENOBSERVE_PASSWORD", "").strip()
|
|
if openobserve_url and (openobserve_token or (openobserve_username and openobserve_password)):
|
|
integrations.append(
|
|
_active_env_record(
|
|
"openobserve",
|
|
{
|
|
"base_url": openobserve_url.rstrip("/"),
|
|
"org": os.getenv("OPENOBSERVE_ORG", "default").strip() or "default",
|
|
"api_token": openobserve_token,
|
|
"username": openobserve_username,
|
|
"password": openobserve_password,
|
|
"stream": os.getenv("OPENOBSERVE_STREAM", "").strip(),
|
|
"max_results": safe_int(os.getenv("OPENOBSERVE_MAX_RESULTS", "100"), 100),
|
|
},
|
|
)
|
|
)
|
|
|
|
opensearch_url = os.getenv("OPENSEARCH_URL", "").strip()
|
|
if opensearch_url:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"opensearch",
|
|
{
|
|
"url": opensearch_url.rstrip("/"),
|
|
"api_key": resolve_env_credential("OPENSEARCH_API_KEY"),
|
|
"username": os.getenv("OPENSEARCH_USERNAME", "").strip(),
|
|
"password": resolve_env_credential("OPENSEARCH_PASSWORD"),
|
|
"index_pattern": os.getenv("OPENSEARCH_INDEX_PATTERN", "*").strip() or "*",
|
|
"max_results": safe_int(os.getenv("OPENSEARCH_MAX_RESULTS", "100"), 100),
|
|
},
|
|
)
|
|
)
|
|
|
|
alertmanager_url = os.getenv("ALERTMANAGER_URL", "").strip().rstrip("/")
|
|
if alertmanager_url:
|
|
try:
|
|
alertmanager_config = AlertmanagerIntegrationConfig.model_validate(
|
|
{
|
|
"base_url": alertmanager_url,
|
|
"bearer_token": os.getenv("ALERTMANAGER_BEARER_TOKEN", "").strip(),
|
|
"username": os.getenv("ALERTMANAGER_USERNAME", "").strip(),
|
|
"password": os.getenv("ALERTMANAGER_PASSWORD", "").strip(),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"alertmanager",
|
|
alertmanager_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="alertmanager")
|
|
|
|
victoria_logs_url = os.getenv("VICTORIA_LOGS_URL", "").strip().rstrip("/")
|
|
if victoria_logs_url:
|
|
try:
|
|
victoria_logs_config = VictoriaLogsIntegrationConfig.model_validate(
|
|
{
|
|
"base_url": victoria_logs_url,
|
|
"tenant_id": os.getenv("VICTORIA_LOGS_TENANT_ID"),
|
|
}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"victoria_logs",
|
|
victoria_logs_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="victoria_logs")
|
|
|
|
splunk_multi = _parse_instances_env("SPLUNK_INSTANCES", "splunk")
|
|
if splunk_multi is not None:
|
|
integrations.append(splunk_multi)
|
|
else:
|
|
splunk_url = os.getenv("SPLUNK_URL", "").strip()
|
|
splunk_token = os.getenv("SPLUNK_TOKEN", "").strip()
|
|
if splunk_url and splunk_token:
|
|
try:
|
|
splunk_config = SplunkIntegrationConfig.model_validate(
|
|
{
|
|
"base_url": splunk_url,
|
|
"token": splunk_token,
|
|
"index": os.getenv("SPLUNK_INDEX", "main").strip(),
|
|
"verify_ssl": os.getenv("SPLUNK_VERIFY_SSL", "true").strip().lower()
|
|
!= "false",
|
|
"ca_bundle": os.getenv("SPLUNK_CA_BUNDLE", "").strip(),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="splunk")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"splunk",
|
|
splunk_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
|
|
supabase_url = os.getenv("SUPABASE_URL", "").strip()
|
|
supabase_service_key = os.getenv("SUPABASE_SERVICE_KEY", "").strip()
|
|
if supabase_url and supabase_service_key:
|
|
try:
|
|
sb_config = build_supabase_config(
|
|
{"url": supabase_url, "service_key": supabase_service_key}
|
|
)
|
|
integrations.append(
|
|
_active_env_record(
|
|
"supabase",
|
|
{"project_url": sb_config.url},
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="supabase")
|
|
|
|
try:
|
|
signoz_config = signoz_config_from_env()
|
|
if signoz_config is not None and signoz_config.is_configured:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"signoz",
|
|
signoz_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="signoz")
|
|
|
|
try:
|
|
jenkins_config = jenkins_config_from_env()
|
|
if jenkins_config is not None and jenkins_config.is_configured:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"jenkins",
|
|
jenkins_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="jenkins")
|
|
|
|
try:
|
|
tempo_config = tempo_config_from_env()
|
|
if tempo_config is not None and tempo_config.is_configured:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"tempo",
|
|
tempo_config.model_dump(exclude={"integration_id"}),
|
|
)
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="tempo")
|
|
|
|
temporal_url = os.getenv("TEMPORAL_API_URL", "").strip()
|
|
temporal_namespace = os.getenv("TEMPORAL_NAMESPACE", "default").strip()
|
|
if temporal_url and temporal_namespace:
|
|
try:
|
|
temporal_config = TemporalConfig.model_validate(
|
|
{
|
|
"base_url": temporal_url,
|
|
"api_key": os.getenv("TEMPORAL_API_KEY", "").strip(),
|
|
"namespace": temporal_namespace,
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
_report_env_loader_failure(exc, integration="temporal")
|
|
else:
|
|
integrations.append(
|
|
_active_env_record(
|
|
"temporal",
|
|
temporal_config.model_dump(),
|
|
)
|
|
)
|
|
|
|
return integrations
|
|
|
|
|
|
def merge_local_integrations(
|
|
store_integrations: list[dict[str, Any]],
|
|
env_integrations: list[dict[str, Any]],
|
|
) -> list[dict[str, Any]]:
|
|
"""Merge local store and env integrations, preferring store entries by service."""
|
|
return merge_integrations_by_service(env_integrations, store_integrations)
|
|
|
|
|
|
def merge_integrations_by_service(
|
|
*integration_groups: list[dict[str, Any]],
|
|
) -> list[dict[str, Any]]:
|
|
"""Merge integration records by service, letting later groups override earlier ones."""
|
|
merged_by_service: dict[str, dict[str, Any]] = {}
|
|
for integration_group in integration_groups:
|
|
for integration in integration_group:
|
|
service = str(integration.get("service", "")).strip()
|
|
if service:
|
|
merged_by_service[service] = integration
|
|
return list(merged_by_service.values())
|
|
|
|
|
|
def _effective_entry(source: str, config: dict[str, Any]) -> dict[str, Any]:
|
|
return {"source": source, "config": config}
|
|
|
|
|
|
def _config_as_dict(config: Any) -> dict[str, Any] | None:
|
|
"""Normalize a classified config (BaseModel or dict) to a plain dict."""
|
|
from pydantic import BaseModel
|
|
|
|
if isinstance(config, BaseModel):
|
|
return config.model_dump(exclude_none=True)
|
|
if isinstance(config, dict) and config:
|
|
return config
|
|
return None
|
|
|
|
|
|
def _publish_classified_effective_service(
|
|
effective: dict[str, dict[str, Any]],
|
|
classified_integrations: dict[str, Any],
|
|
source_by_service: dict[str, str],
|
|
service: str,
|
|
) -> None:
|
|
"""Copy a directly classified service into the effective view."""
|
|
resolved_integration = classified_integrations.get(service)
|
|
config_dict = _config_as_dict(resolved_integration)
|
|
if config_dict is None:
|
|
return
|
|
|
|
effective[service] = _effective_entry(
|
|
source_by_service.get(service, "local env"),
|
|
config_dict,
|
|
)
|
|
all_instances = classified_integrations.get(f"_all_{service}_instances")
|
|
if _should_publish_instance_siblings(all_instances) and isinstance(all_instances, list):
|
|
# Convert any BaseModel configs to dicts in the instances list
|
|
normalized_instances = [
|
|
{**inst, "config": _config_as_dict(inst.get("config")) or {}}
|
|
if isinstance(inst, dict)
|
|
else inst
|
|
for inst in all_instances
|
|
]
|
|
effective[service]["instances"] = normalized_instances
|
|
|
|
|
|
def _service_metadata(
|
|
store_integrations: list[dict[str, Any]],
|
|
env_integrations: list[dict[str, Any]],
|
|
) -> tuple[dict[str, str], dict[str, dict[str, Any]]]:
|
|
source_by_service: dict[str, str] = {}
|
|
store_integration_by_service: dict[str, dict[str, Any]] = {}
|
|
|
|
for integration in env_integrations:
|
|
service = str(integration.get("service", "")).strip().lower()
|
|
if service:
|
|
source_by_service[service] = "local env"
|
|
|
|
for integration in store_integrations:
|
|
service = str(integration.get("service", "")).strip().lower()
|
|
if service:
|
|
source_by_service[service] = "local store"
|
|
store_integration_by_service.setdefault(service, integration)
|
|
|
|
return source_by_service, store_integration_by_service
|
|
|
|
|
|
def _raw_credentials(config: dict[str, Any]) -> dict[str, Any]:
|
|
credentials = config.get("credentials")
|
|
if isinstance(credentials, dict):
|
|
return credentials
|
|
|
|
instances = config.get("instances")
|
|
if isinstance(instances, list):
|
|
for instance in instances:
|
|
if not isinstance(instance, dict):
|
|
continue
|
|
instance_credentials = instance.get("credentials")
|
|
if isinstance(instance_credentials, dict):
|
|
return instance_credentials
|
|
|
|
return config
|
|
|
|
|
|
def resolve_effective_integrations(
|
|
*,
|
|
store_integrations: list[dict[str, Any]] | None = None,
|
|
env_integrations: list[dict[str, Any]] | None = None,
|
|
) -> dict[str, dict[str, Any]]:
|
|
"""Resolve effective local integrations from ~/.opensre and environment variables."""
|
|
store_records = (
|
|
list(store_integrations) if store_integrations is not None else load_integrations()
|
|
)
|
|
env_records = (
|
|
list(env_integrations) if env_integrations is not None else load_env_integrations()
|
|
)
|
|
merged_integrations = merge_local_integrations(store_records, env_records)
|
|
classified_integrations = classify_integrations(merged_integrations)
|
|
source_by_service, store_integration_by_service = _service_metadata(store_records, env_records)
|
|
|
|
effective: dict[str, dict[str, Any]] = {}
|
|
|
|
for service in DIRECT_CLASSIFIED_EFFECTIVE_SERVICES:
|
|
_publish_classified_effective_service(
|
|
effective,
|
|
classified_integrations,
|
|
source_by_service,
|
|
service,
|
|
)
|
|
|
|
if "datadog" not in effective:
|
|
datadog_store_integration = store_integration_by_service.get("datadog")
|
|
if isinstance(datadog_store_integration, dict):
|
|
datadog_credentials = _raw_credentials(datadog_store_integration)
|
|
effective["datadog"] = _effective_entry(
|
|
"local store",
|
|
{
|
|
"api_key": str(datadog_credentials.get("api_key", "")).strip(),
|
|
"app_key": str(datadog_credentials.get("app_key", "")).strip(),
|
|
"site": str(datadog_credentials.get("site", "datadoghq.com")).strip()
|
|
or "datadoghq.com",
|
|
"integration_id": str(datadog_store_integration.get("id", "")).strip(),
|
|
},
|
|
)
|
|
|
|
tracer_integration = classified_integrations.get("tracer")
|
|
if isinstance(tracer_integration, dict):
|
|
tracer_credentials = _raw_credentials(tracer_integration)
|
|
effective["tracer"] = _effective_entry(
|
|
source_by_service.get("tracer", "local store"),
|
|
{
|
|
"base_url": str(tracer_credentials.get("base_url", "")).strip(),
|
|
"jwt_token": str(tracer_credentials.get("jwt_token", "")).strip(),
|
|
},
|
|
)
|
|
else:
|
|
jwt_token = os.getenv("JWT_TOKEN", "").strip()
|
|
if jwt_token:
|
|
effective["tracer"] = _effective_entry(
|
|
"local env",
|
|
{
|
|
"base_url": os.getenv("TRACER_API_URL", "").strip() or get_tracer_base_url(),
|
|
"jwt_token": jwt_token,
|
|
},
|
|
)
|
|
|
|
slack_store_integration = store_integration_by_service.get("slack")
|
|
if isinstance(slack_store_integration, dict):
|
|
slack_credentials = _raw_credentials(slack_store_integration)
|
|
webhook_url = str(slack_credentials.get("webhook_url", "")).strip()
|
|
if webhook_url:
|
|
try:
|
|
slack_config = SlackWebhookConfig.model_validate({"webhook_url": webhook_url})
|
|
effective["slack"] = _effective_entry("local store", slack_config.model_dump())
|
|
except Exception:
|
|
# Do NOT include the exception value — Pydantic v2 ValidationError
|
|
# embeds the input_value (here a SlackWebhookConfig containing the
|
|
# webhook_url) in its string representation, and Slack webhook URLs
|
|
# carry a secret token in the path. Log only a static message.
|
|
logger.warning("Slack webhook URL from store is invalid; skipping Slack")
|
|
elif slack_webhook_url := os.getenv("SLACK_WEBHOOK_URL", "").strip():
|
|
try:
|
|
slack_config = SlackWebhookConfig.model_validate({"webhook_url": slack_webhook_url})
|
|
effective["slack"] = _effective_entry("local env", slack_config.model_dump())
|
|
except Exception:
|
|
# See note above: avoid logging the ValidationError which embeds the
|
|
# raw webhook_url (and its secret token).
|
|
logger.warning("SLACK_WEBHOOK_URL is invalid; skipping Slack")
|
|
|
|
google_docs_integration = classified_integrations.get("google_docs")
|
|
if isinstance(google_docs_integration, dict):
|
|
google_docs_credentials = _raw_credentials(google_docs_integration)
|
|
effective["google_docs"] = _effective_entry(
|
|
source_by_service.get("google_docs", "local env"),
|
|
{
|
|
"credentials_file": str(
|
|
google_docs_credentials.get("credentials_file", "")
|
|
).strip(),
|
|
"folder_id": str(google_docs_credentials.get("folder_id", "")).strip(),
|
|
},
|
|
)
|
|
else:
|
|
credentials_file = os.getenv("GOOGLE_CREDENTIALS_FILE", "").strip()
|
|
folder_id = os.getenv("GOOGLE_DRIVE_FOLDER_ID", "").strip()
|
|
if credentials_file and folder_id:
|
|
effective["google_docs"] = _effective_entry(
|
|
"local env",
|
|
{
|
|
"credentials_file": credentials_file,
|
|
"folder_id": folder_id,
|
|
},
|
|
)
|
|
|
|
kafka_integration = classified_integrations.get("kafka")
|
|
if isinstance(kafka_integration, dict):
|
|
kafka_credentials = _raw_credentials(kafka_integration)
|
|
effective["kafka"] = _effective_entry(
|
|
source_by_service.get("kafka", "local env"),
|
|
{
|
|
"bootstrap_servers": str(kafka_credentials.get("bootstrap_servers", "")).strip(),
|
|
"security_protocol": str(
|
|
kafka_credentials.get("security_protocol", "PLAINTEXT")
|
|
).strip(),
|
|
"sasl_mechanism": str(kafka_credentials.get("sasl_mechanism", "")).strip(),
|
|
"sasl_username": str(kafka_credentials.get("sasl_username", "")).strip(),
|
|
"sasl_password": str(kafka_credentials.get("sasl_password", "")).strip(),
|
|
},
|
|
)
|
|
else:
|
|
kafka_servers = os.getenv("KAFKA_BOOTSTRAP_SERVERS", "").strip()
|
|
if kafka_servers:
|
|
effective["kafka"] = _effective_entry(
|
|
"local env",
|
|
{
|
|
"bootstrap_servers": kafka_servers,
|
|
"security_protocol": os.getenv("KAFKA_SECURITY_PROTOCOL", "PLAINTEXT").strip(),
|
|
"sasl_mechanism": os.getenv("KAFKA_SASL_MECHANISM", "").strip(),
|
|
"sasl_username": os.getenv("KAFKA_SASL_USERNAME", "").strip(),
|
|
"sasl_password": os.getenv("KAFKA_SASL_PASSWORD", "").strip(),
|
|
},
|
|
)
|
|
|
|
clickhouse_integration = classified_integrations.get("clickhouse")
|
|
if isinstance(clickhouse_integration, dict):
|
|
clickhouse_credentials = _raw_credentials(clickhouse_integration)
|
|
effective["clickhouse"] = _effective_entry(
|
|
source_by_service.get("clickhouse", "local env"),
|
|
{
|
|
"host": str(clickhouse_credentials.get("host", "")).strip(),
|
|
"port": clickhouse_credentials.get("port", 8123),
|
|
"database": str(clickhouse_credentials.get("database", "default")).strip(),
|
|
"username": str(clickhouse_credentials.get("username", "default")).strip(),
|
|
"password": str(clickhouse_credentials.get("password", "")).strip(),
|
|
"secure": clickhouse_credentials.get("secure", False),
|
|
},
|
|
)
|
|
else:
|
|
clickhouse_host = os.getenv("CLICKHOUSE_HOST", "").strip()
|
|
if clickhouse_host:
|
|
effective["clickhouse"] = _effective_entry(
|
|
"local env",
|
|
{
|
|
"host": clickhouse_host,
|
|
"port": int(os.getenv("CLICKHOUSE_PORT", "8123") or "8123"),
|
|
"database": os.getenv("CLICKHOUSE_DATABASE", "default").strip(),
|
|
"username": os.getenv("CLICKHOUSE_USER", "default").strip(),
|
|
"password": os.getenv("CLICKHOUSE_PASSWORD", "").strip(),
|
|
"secure": os.getenv("CLICKHOUSE_SECURE", "false").strip().lower()
|
|
in ("true", "1", "yes"),
|
|
},
|
|
)
|
|
|
|
known_keys = set(EffectiveIntegrations.model_fields)
|
|
unknown_keys = set(effective) - known_keys
|
|
if unknown_keys:
|
|
logger.warning(
|
|
"resolve_effective_integrations: dropping unrecognised integration key(s): %s",
|
|
sorted(unknown_keys),
|
|
)
|
|
filtered_effective = {k: v for k, v in effective.items() if k in known_keys}
|
|
return EffectiveIntegrations.model_validate(filtered_effective).model_dump(exclude_none=True)
|