Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,131 @@
|
||||
"""Typed HTTP client for the internal Docker read broker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
from typing import Any, Protocol
|
||||
|
||||
import httpx
|
||||
|
||||
from packages.connector_kit import ConnectorContext
|
||||
from packages.hub_runtime import issue_broker_service_token
|
||||
|
||||
|
||||
class BrokerClientError(RuntimeError):
|
||||
def __init__(self, code: str, message: str, status: int = 502) -> None:
|
||||
self.code = code
|
||||
self.status = status
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BrokerTokenProvider(Protocol):
|
||||
def get_token(self, context: ConnectorContext) -> str: ...
|
||||
|
||||
|
||||
class FileBrokerTokenProvider:
|
||||
def __init__(self, path: str = "/run/secrets/docker_broker_service_token") -> None:
|
||||
self.path = Path(path)
|
||||
|
||||
def get_token(self, context: ConnectorContext) -> str:
|
||||
try:
|
||||
secret = self.path.read_text(encoding="utf-8").strip()
|
||||
except OSError as exc:
|
||||
raise BrokerClientError(
|
||||
"BROKER_TOKEN_UNAVAILABLE", "broker token is unavailable", 503
|
||||
) from exc
|
||||
if len(secret) < 32:
|
||||
raise BrokerClientError("BROKER_TOKEN_INVALID", "broker signing key is invalid", 503)
|
||||
return issue_broker_service_token(
|
||||
secret,
|
||||
context.project_slug,
|
||||
context.environment,
|
||||
ttl_seconds=60,
|
||||
)
|
||||
|
||||
|
||||
class HttpBrokerClient:
|
||||
"""Only named broker operations exist; arbitrary paths are not accepted."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
token_provider: BrokerTokenProvider,
|
||||
*,
|
||||
transport: httpx.BaseTransport | None = None,
|
||||
) -> None:
|
||||
if not base_url.startswith("http://") or any(char in base_url for char in "?#"):
|
||||
raise ValueError("broker base URL must be an internal HTTP origin without query data")
|
||||
self._client = httpx.Client(base_url=base_url.rstrip("/"), timeout=10, transport=transport)
|
||||
self._token_provider = token_provider
|
||||
|
||||
def close(self) -> None:
|
||||
self._client.close()
|
||||
|
||||
def _get(
|
||||
self, context: ConnectorContext, path: str, params: Mapping[str, str | int] | None = None
|
||||
) -> dict[str, Any]:
|
||||
token = self._token_provider.get_token(context)
|
||||
try:
|
||||
response = self._client.get(
|
||||
path, params=params, headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
except (httpx.TimeoutException, httpx.NetworkError) as exc:
|
||||
raise BrokerClientError(
|
||||
"BROKER_UNAVAILABLE", "Docker read broker is unavailable", 503
|
||||
) from exc
|
||||
if response.status_code >= 400:
|
||||
try:
|
||||
detail = response.json()
|
||||
except ValueError:
|
||||
detail = {}
|
||||
raise BrokerClientError(
|
||||
str(detail.get("code", "BROKER_REQUEST_FAILED")),
|
||||
"Docker read broker denied the request",
|
||||
response.status_code,
|
||||
)
|
||||
try:
|
||||
value = response.json()
|
||||
except ValueError as exc:
|
||||
raise BrokerClientError(
|
||||
"BROKER_INVALID_RESPONSE", "Docker read broker returned invalid JSON"
|
||||
) from exc
|
||||
if not isinstance(value, dict):
|
||||
raise BrokerClientError(
|
||||
"BROKER_INVALID_RESPONSE", "Docker read broker returned an unexpected response"
|
||||
)
|
||||
return value
|
||||
|
||||
def host(self, context: ConnectorContext) -> dict[str, Any]:
|
||||
return self._get(context, "/v1/host")
|
||||
|
||||
def storage(self, context: ConnectorContext) -> dict[str, Any]:
|
||||
return self._get(context, "/v1/storage")
|
||||
|
||||
def containers(self, context: ConnectorContext, state: str | None = None) -> dict[str, Any]:
|
||||
return self._get(context, "/v1/containers", {"state": state} if state else None)
|
||||
|
||||
def inspect(self, context: ConnectorContext, registered_id: str) -> dict[str, Any]:
|
||||
return self._get(context, f"/v1/containers/{registered_id}")
|
||||
|
||||
def health(self, context: ConnectorContext, registered_id: str) -> dict[str, Any]:
|
||||
return self._get(context, f"/v1/containers/{registered_id}/health")
|
||||
|
||||
def logs(
|
||||
self,
|
||||
context: ConnectorContext,
|
||||
registered_id: str,
|
||||
*,
|
||||
tail: int,
|
||||
since_seconds: int | None,
|
||||
contains: str | None,
|
||||
) -> dict[str, Any]:
|
||||
params: dict[str, str | int] = {"tail": tail}
|
||||
if since_seconds is not None:
|
||||
params["sinceSeconds"] = since_seconds
|
||||
if contains:
|
||||
params["contains"] = contains
|
||||
return self._get(context, f"/v1/containers/{registered_id}/logs", params)
|
||||
|
||||
def networks(self, context: ConnectorContext) -> dict[str, Any]:
|
||||
return self._get(context, "/v1/networks")
|
||||
Reference in new issue
Block a user