File size: 5,548 Bytes
c308a36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# SPDX-License-Identifier: Apache-2.0
# © 2026 Lutar, Stephen P. — SZL Holdings · Doctrine v11 · Perplexity Computer Agent.
"""Observability connectors — Datadog, Splunk, Grafana.

REAL clients against documented endpoints. NO creds → READY + exact secret name;
NEVER fabricates a record. Reads are monitor/alert/dashboard listings.

API refs (publicly documented shapes):
  Datadog API v1     https://docs.datadoghq.com/api/latest/monitors/
  Splunk REST        https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTsearch
  Grafana HTTP API   https://grafana.com/docs/grafana/latest/developers/http_api/
"""
from __future__ import annotations

import base64
import os

from ..base import State, Records, http_json
from ..ready import ReadyConnector
from ..registry import register


# ── Datadog (api_key + app key headers; free tier) ────────────────────────────
@register
class DatadogConnector(ReadyConnector):
    id = "datadog"
    label = "Datadog"
    category = "observability"
    auth_kind = "api_key"
    free_tier = True  # free monitoring tier
    env_vars = ["SZL_DATADOG_API_KEY", "SZL_DATADOG_APP_KEY", "SZL_DATADOG_SITE"]
    _primary_secret = "SZL_DATADOG_API_KEY"
    provider_base = "https://api.{site}/api/v1"
    docs_url = "https://docs.datadoghq.com/api/latest/monitors/"
    schema_preview = ["id", "name", "type", "overall_state"]

    def _base_url(self):
        site = os.environ.get("SZL_DATADOG_SITE", "datadoghq.com")
        return self.provider_base.replace("{site}", site)

    def _auth_header(self):
        api = os.environ.get("SZL_DATADOG_API_KEY", "")
        app = os.environ.get("SZL_DATADOG_APP_KEY", "")
        if not api:
            return {}
        return {"DD-API-KEY": api, "DD-APPLICATION-KEY": app}

    def read(self, query=None):
        if self._primary_missing():
            return self._ready_records(
                "provide credentials to activate — set SZL_DATADOG_API_KEY, "
                "SZL_DATADOG_APP_KEY (+ optional SZL_DATADOG_SITE). Lists monitors.")
        url = self._base_url() + "/monitor?page_size=10"
        st, raw = http_json(url, headers={"Accept": "application/json", **self._auth_header()})
        if st == 200 and isinstance(raw, list):
            out = [{k: r.get(k) for k in self.schema_preview if k in r} for r in raw[:10]]
            return Records(connector_id=self.id, category=self.category, state=State.CONNECTED,
                           records=out, source="Datadog monitors", live=True,
                           note=f"live · {len(raw)} monitors", schema_preview=self.schema_preview)
        return Records(connector_id=self.id, category=self.category, state=State.ERROR,
                       records=[], source=self.provider_base, live=False,
                       note=f"credentials present but Datadog HTTP {st}", schema_preview=self.schema_preview)


# ── Splunk (REST; basic/token auth; free trial) ───────────────────────────────
@register
class SplunkConnector(ReadyConnector):
    id = "splunk"
    label = "Splunk"
    category = "observability"
    auth_kind = "api_key"  # bearer token or basic
    free_tier = False
    env_vars = ["SZL_SPLUNK_HOST", "SZL_SPLUNK_TOKEN"]
    _primary_secret = "SZL_SPLUNK_TOKEN"
    provider_base = "{host}/services"
    docs_url = "https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTsearch"
    schema_preview = ["title", "disabled", "search"]
    _read_path = "saved/searches?output_mode=json&count=10"
    _record_path = "entry"

    def _base_url(self):
        return self.provider_base.replace("{host}",
                                          os.environ.get("SZL_SPLUNK_HOST", "").rstrip("/"))

    def _auth_header(self):
        tok = os.environ.get("SZL_SPLUNK_TOKEN")
        return {"Authorization": f"Bearer {tok}"} if tok else {}

    def _dig(self, raw):
        rows = (raw or {}).get("entry", []) if isinstance(raw, dict) else []
        out = []
        for r in rows:
            c = r.get("content", {}) if isinstance(r, dict) else {}
            out.append({"title": r.get("name"), "disabled": c.get("disabled"),
                        "search": c.get("search")})
        return out


# ── Grafana (api_key service-account token; free OSS/Cloud) ───────────────────
@register
class GrafanaConnector(ReadyConnector):
    id = "grafana"
    label = "Grafana"
    category = "observability"
    auth_kind = "api_key"
    free_tier = True  # OSS + free cloud tier
    env_vars = ["SZL_GRAFANA_URL", "SZL_GRAFANA_TOKEN"]
    _primary_secret = "SZL_GRAFANA_TOKEN"
    provider_base = "{url}/api"
    docs_url = "https://grafana.com/docs/grafana/latest/developers/http_api/"
    schema_preview = ["id", "uid", "title", "type"]
    _read_path = "search?limit=10"
    _record_path = ""

    def _base_url(self):
        return self.provider_base.replace("{url}",
                                          os.environ.get("SZL_GRAFANA_URL", "").rstrip("/"))

    def _auth_header(self):
        tok = os.environ.get("SZL_GRAFANA_TOKEN")
        return {"Authorization": f"Bearer {tok}"} if tok else {}

    def _dig(self, raw):
        rows = raw if isinstance(raw, list) else []
        return [{k: r.get(k) for k in self.schema_preview if k in r}
                for r in rows if isinstance(r, dict)]


__all__ = ["DatadogConnector", "SplunkConnector", "GrafanaConnector"]