mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
236 lines
7.5 KiB
Python
236 lines
7.5 KiB
Python
"""HTTP and Billing adapters used by the network access group service."""
|
|
|
|
import json
|
|
from typing import Any, Literal, override
|
|
|
|
import httpx
|
|
from pydantic import ValidationError
|
|
from tenacity import retry, retry_if_exception_type, stop_before_delay, wait_fixed
|
|
|
|
from core.helper.http_client_pooling import get_pooled_http_client
|
|
from enums import CloudPlan
|
|
from services.billing_service import BillingService, _BillingHTTPStatusError
|
|
from services.network_access_group_service import (
|
|
NetworkAccessGroupControlPlane,
|
|
NetworkAccessGroupEntitlement,
|
|
NetworkAccessGroupEntitlementUnavailableError,
|
|
NetworkAccessGroupUpstreamError,
|
|
)
|
|
|
|
_http_client: httpx.Client = get_pooled_http_client(
|
|
"network-access-group:default",
|
|
lambda: httpx.Client(
|
|
timeout=httpx.Timeout(30.0, connect=5.0),
|
|
limits=httpx.Limits(max_keepalive_connections=50, max_connections=100),
|
|
),
|
|
)
|
|
|
|
|
|
class NetworkAccessGroupGateway(NetworkAccessGroupControlPlane):
|
|
def __init__(
|
|
self,
|
|
*,
|
|
base_url: str,
|
|
fallback_base_url: str,
|
|
secret_key: str,
|
|
http_client: httpx.Client = _http_client,
|
|
) -> None:
|
|
self._base_url = base_url.strip() or fallback_base_url
|
|
self._secret_key = secret_key
|
|
self._http_client = http_client
|
|
|
|
@override
|
|
def list_groups(self, workspace_id: str, actor_account_id: str) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"GET",
|
|
f"/tenants/{workspace_id}/network-access-groups",
|
|
params={"actor_account_id": actor_account_id},
|
|
)
|
|
|
|
@override
|
|
def create_group(
|
|
self,
|
|
workspace_id: str,
|
|
*,
|
|
name: str,
|
|
description: str,
|
|
allowed_cidrs: list[str],
|
|
actor_account_id: str,
|
|
) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"POST",
|
|
f"/tenants/{workspace_id}/network-access-groups",
|
|
payload_json={
|
|
"name": name,
|
|
"description": description,
|
|
"allowed_cidrs": allowed_cidrs,
|
|
"actor_account_id": actor_account_id,
|
|
},
|
|
)
|
|
|
|
@override
|
|
def get_group(self, workspace_id: str, group_id: str, actor_account_id: str) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"GET",
|
|
f"/tenants/{workspace_id}/network-access-groups/{group_id}",
|
|
params={"actor_account_id": actor_account_id},
|
|
)
|
|
|
|
@override
|
|
def update_group(
|
|
self,
|
|
workspace_id: str,
|
|
group_id: str,
|
|
*,
|
|
name: str,
|
|
description: str,
|
|
allowed_cidrs: list[str],
|
|
expected_version: int,
|
|
actor_account_id: str,
|
|
) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"PUT",
|
|
f"/tenants/{workspace_id}/network-access-groups/{group_id}",
|
|
payload_json={
|
|
"name": name,
|
|
"description": description,
|
|
"allowed_cidrs": allowed_cidrs,
|
|
"expected_version": expected_version,
|
|
"actor_account_id": actor_account_id,
|
|
},
|
|
)
|
|
|
|
@override
|
|
def delete_group(
|
|
self,
|
|
workspace_id: str,
|
|
group_id: str,
|
|
*,
|
|
expected_version: int,
|
|
actor_account_id: str,
|
|
) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"DELETE",
|
|
f"/tenants/{workspace_id}/network-access-groups/{group_id}",
|
|
params={
|
|
"expected_version": expected_version,
|
|
"actor_account_id": actor_account_id,
|
|
},
|
|
)
|
|
|
|
@override
|
|
def get_app_binding(self, workspace_id: str, app_id: str, actor_account_id: str) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"GET",
|
|
f"/tenants/{workspace_id}/apps/{app_id}/network-access-group",
|
|
params={"actor_account_id": actor_account_id},
|
|
)
|
|
|
|
@override
|
|
def update_app_binding(
|
|
self,
|
|
workspace_id: str,
|
|
app_id: str,
|
|
*,
|
|
enabled: bool,
|
|
group_id: str | None,
|
|
access_points: list[str],
|
|
expected_version: int,
|
|
actor_account_id: str,
|
|
) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"PUT",
|
|
f"/tenants/{workspace_id}/apps/{app_id}/network-access-group",
|
|
payload_json={
|
|
"enabled": enabled,
|
|
"group_id": group_id,
|
|
"access_points": access_points,
|
|
"expected_version": expected_version,
|
|
"actor_account_id": actor_account_id,
|
|
},
|
|
)
|
|
|
|
@override
|
|
def cleanup_app_binding(self, workspace_id: str, app_id: str) -> dict[str, Any]:
|
|
return self._send_request(
|
|
"DELETE",
|
|
f"/tenants/{workspace_id}/apps/{app_id}/network-access-group-binding",
|
|
)
|
|
|
|
def _send_request(
|
|
self,
|
|
method: Literal["GET", "POST", "PUT", "DELETE"],
|
|
endpoint: str,
|
|
*,
|
|
payload_json: dict[str, Any] | None = None,
|
|
params: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
try:
|
|
response = self._send_http_request(
|
|
method,
|
|
endpoint,
|
|
payload_json=payload_json,
|
|
params=params,
|
|
)
|
|
except httpx.RequestError as exc:
|
|
raise NetworkAccessGroupUpstreamError(httpx.codes.SERVICE_UNAVAILABLE) from exc
|
|
|
|
if response.status_code not in (httpx.codes.OK, httpx.codes.CREATED):
|
|
reason = None
|
|
try:
|
|
error_payload = response.json()
|
|
if isinstance(error_payload, dict) and isinstance(error_payload.get("reason"), str):
|
|
reason = error_payload["reason"]
|
|
except (json.JSONDecodeError, TypeError, ValueError):
|
|
pass
|
|
raise NetworkAccessGroupUpstreamError(response.status_code, reason)
|
|
|
|
try:
|
|
payload = response.json()
|
|
except (json.JSONDecodeError, TypeError, ValueError) as exc:
|
|
raise NetworkAccessGroupUpstreamError(httpx.codes.BAD_GATEWAY) from exc
|
|
|
|
if not isinstance(payload, dict):
|
|
raise NetworkAccessGroupUpstreamError(httpx.codes.BAD_GATEWAY)
|
|
return payload
|
|
|
|
@retry(
|
|
wait=wait_fixed(2),
|
|
stop=stop_before_delay(10),
|
|
retry=retry_if_exception_type(httpx.RequestError),
|
|
reraise=True,
|
|
)
|
|
def _send_http_request(
|
|
self,
|
|
method: Literal["GET", "POST", "PUT", "DELETE"],
|
|
endpoint: str,
|
|
*,
|
|
payload_json: dict[str, Any] | None = None,
|
|
params: dict[str, Any] | None = None,
|
|
) -> httpx.Response:
|
|
headers = {"Content-Type": "application/json", "Billing-Api-Secret-Key": self._secret_key}
|
|
return self._http_client.request(
|
|
method,
|
|
f"{self._base_url.rstrip('/')}{endpoint}",
|
|
json=payload_json,
|
|
params=params,
|
|
headers=headers,
|
|
follow_redirects=True,
|
|
)
|
|
|
|
|
|
class BillingNetworkAccessGroupEntitlementGateway(NetworkAccessGroupEntitlement):
|
|
@override
|
|
def is_paid_plan(self, workspace_id: str) -> bool:
|
|
try:
|
|
billing_info = BillingService.get_info(workspace_id, exclude_vector_space=True)
|
|
except (
|
|
httpx.RequestError,
|
|
json.JSONDecodeError,
|
|
UnicodeDecodeError,
|
|
ValidationError,
|
|
_BillingHTTPStatusError,
|
|
) as exc:
|
|
raise NetworkAccessGroupEntitlementUnavailableError from exc
|
|
return billing_info["subscription"]["plan"] in (CloudPlan.PROFESSIONAL, CloudPlan.TEAM)
|