New /datto-edr skill — standalone CLI for the Datto EDR REST API (azcomp4587, rebranded Infocyte HUNT, raw-token LoopBack). Live-verified reads across the whole MSP fleet: orgs/sites/agents/detections/sweep + deploy-cmd. Scan/isolate gated behind --confirm (shape-verified, not run against prod). Token vaulted at msp-tools/datto-edr.sops.yaml. Also: reference_syncro_rmm_api_gui_only memory (Syncro RMM policy mgmt is GUI-only) and the guru-rmm submodule pointer bump (Feature 6 EDR research). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Self-test for the datto-edr skill. Read-only; never mutates the tenant.
|
|
|
|
Confirms: token loads from vault, transport works, and the core read endpoints
|
|
return live data. Exits non-zero on any failure. Set EDR_SUPPRESS_ERRORLOG=1 so
|
|
expected probe errors don't spam errorlog.md.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
os.environ.setdefault("EDR_SUPPRESS_ERRORLOG", "1")
|
|
|
|
from edr_client import DattoEDRClient, DattoEDRError
|
|
|
|
|
|
def main() -> int:
|
|
checks: list[tuple[str, bool, str]] = []
|
|
try:
|
|
client = DattoEDRClient()
|
|
_ = client.api_token
|
|
checks.append(("vault token load", True, ""))
|
|
except DattoEDRError as exc:
|
|
print(f"[FAIL] token load: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
def check(name, fn):
|
|
try:
|
|
val = fn()
|
|
checks.append((name, True, str(val)))
|
|
except Exception as exc: # noqa: BLE001 - selftest summarizes failures
|
|
checks.append((name, False, str(exc)))
|
|
|
|
check("status rollup", lambda: client.get_status()["agents"])
|
|
check("list organizations", lambda: f"{len(client.list_organizations())} orgs")
|
|
check("list locations", lambda: f"{len(client.list_locations())} sites")
|
|
check("list targets", lambda: f"{len(client.list_targets())} scan groups")
|
|
check("list agents (limit 5)", lambda: f"{len(client.list_agents(limit=5))} agents")
|
|
check("list alerts (limit 5)", lambda: f"{len(client.list_alerts(limit=5))} alerts")
|
|
|
|
ok = True
|
|
for name, passed, detail in checks:
|
|
marker = "[OK]" if passed else "[FAIL]"
|
|
print(f"{marker} {name}{(' -> ' + detail) if detail else ''}")
|
|
ok = ok and passed
|
|
return 0 if ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|