From 128e7db95b0ab64a4fc67a4820c60c09072fb07e Mon Sep 17 00:00:00 2001 From: Issac-Newton <1556820213@qq.com> Date: Tue, 16 Jun 2026 15:59:23 +0800 Subject: [PATCH] feat(admin): add GET /server-config endpoint for public platform config Co-Authored-By: Claude Opus 4.6 --- rock-conf/rock-local.yml | 8 +++++ rock/admin/entrypoints/sandbox_proxy_api.py | 8 +++++ rock/config.py | 22 ++++++++++++ rock/sandbox/service/sandbox_proxy_service.py | 18 +++++++++- tests/unit/sandbox/test_sandbox_proxy.py | 35 ++++++++++++++++++- 5 files changed, 89 insertions(+), 2 deletions(-) diff --git a/rock-conf/rock-local.yml b/rock-conf/rock-local.yml index c7bb4f3049..4bbb6c048c 100644 --- a/rock-conf/rock-local.yml +++ b/rock-conf/rock-local.yml @@ -34,6 +34,14 @@ warmup: # - "reg-a.aliyuncs.com/mirror-1" # - "reg-b.aliyuncs.com/mirror-2" +# Server public configuration (exposed via GET /server/config) +server: + image_registries: [] + # - namespace: "rock" + # registry_url: "" + # region: "cn-hangzhou" + builder_image: "" + # Scheduler configuration scheduler: enabled: true # Whether to enable the scheduler diff --git a/rock/admin/entrypoints/sandbox_proxy_api.py b/rock/admin/entrypoints/sandbox_proxy_api.py index 510e4b3e02..ecb890baac 100644 --- a/rock/admin/entrypoints/sandbox_proxy_api.py +++ b/rock/admin/entrypoints/sandbox_proxy_api.py @@ -320,6 +320,14 @@ async def portforward(websocket: WebSocket, id: str, port: int): await websocket.close(code=1011, reason=f"Proxy error: {str(e)}") +@sandbox_proxy_router.get("/server-config") +@handle_exceptions(error_message="get server config failed") +async def get_server_config(): + """Return server public configuration.""" + result = sandbox_proxy_service.get_server_config() + return RockResponse(result=result) + + @sandbox_proxy_router.get("/get_token") @handle_exceptions(error_message="get oss sts token failed") async def get_token(account: str = "legacy"): diff --git a/rock/config.py b/rock/config.py index 712bde5945..96a19433c6 100644 --- a/rock/config.py +++ b/rock/config.py @@ -152,6 +152,25 @@ def __post_init__(self): self.primary = OssAccountConfig(**self.primary) +@dataclass +class ImageRegistryConfig: + namespace: str = "rock" + registry_url: str | None = None + region: str | None = None + + +@dataclass +class ServerConfig: + image_registries: list[ImageRegistryConfig] = field(default_factory=list) + builder_image: str = "" + + def __post_init__(self): + self.image_registries = [ + ImageRegistryConfig(**item) if isinstance(item, dict) else item + for item in self.image_registries + ] + + @dataclass class ProxyServiceConfig: timeout: float = 180.0 @@ -348,6 +367,7 @@ class RockConfig: redis: RedisConfig = field(default_factory=RedisConfig) sandbox_config: SandboxConfig = field(default_factory=SandboxConfig) oss: OssConfig = field(default_factory=OssConfig) + server: ServerConfig = field(default_factory=ServerConfig) runtime: RuntimeConfig = field(default_factory=RuntimeConfig) proxy_service: ProxyServiceConfig = field(default_factory=ProxyServiceConfig) scheduler: SchedulerConfig = field(default_factory=SchedulerConfig) @@ -401,6 +421,8 @@ def from_env(cls, config_path: str | None = None): kwargs["sandbox_config"] = SandboxConfig(**config["sandbox_config"]) if "oss" in config: kwargs["oss"] = OssConfig(**config["oss"]) + if "server" in config: + kwargs["server"] = ServerConfig(**config["server"]) if "runtime" in config: kwargs["runtime"] = RuntimeConfig(**config["runtime"]) if "proxy_service" in config: diff --git a/rock/sandbox/service/sandbox_proxy_service.py b/rock/sandbox/service/sandbox_proxy_service.py index e42564a7bd..32200f59aa 100644 --- a/rock/sandbox/service/sandbox_proxy_service.py +++ b/rock/sandbox/service/sandbox_proxy_service.py @@ -34,7 +34,7 @@ from rock.admin.proto.request import SandboxReadFileRequest as ReadFileRequest from rock.admin.proto.request import SandboxWriteFileRequest as WriteFileRequest from rock.admin.proto.response import SandboxListResponse, SandboxListStatusResponse, SandboxStatusResponse -from rock.config import OssConfig, ProxyServiceConfig, RockConfig +from rock.config import OssConfig, ProxyServiceConfig, RockConfig, ServerConfig from rock.deployments.constants import Port from rock.deployments.status import ServiceStatus from rock.common.port_validation import validate_port_forward_port @@ -91,6 +91,8 @@ def __init__(self, rock_config: RockConfig, meta_store: SandboxMetaStore): primary_region, ) + self.server_config: ServerConfig = rock_config.server + self._batch_get_status_max_count = rock_config.proxy_service.batch_get_status_max_count self._validate_oss_config_or_warn() @@ -746,6 +748,20 @@ def gen_oss_sts_token( "Prefix": prefix, # transfer-object key prefix, scoped per account } + def get_server_config(self) -> dict: + """Return server public configuration.""" + return { + "ImageRegistries": [ + { + "Namespace": r.namespace, + "RegistryUrl": r.registry_url, + "Region": r.region, + } + for r in self.server_config.image_registries + ], + "BuilderImage": self.server_config.builder_image, + } + async def get_sandbox_websocket_url( self, sandbox_id: str, target_path: str | None = None, port: int | None = None ) -> str: diff --git a/tests/unit/sandbox/test_sandbox_proxy.py b/tests/unit/sandbox/test_sandbox_proxy.py index 5a117a0642..a73f5fa7a1 100644 --- a/tests/unit/sandbox/test_sandbox_proxy.py +++ b/tests/unit/sandbox/test_sandbox_proxy.py @@ -4,7 +4,7 @@ import pytest from rock.actions.sandbox.response import State -from rock.config import OssConfig +from rock.config import ImageRegistryConfig, OssConfig, ServerConfig from rock.deployments.config import DockerDeploymentConfig from rock.sandbox.sandbox_manager import SandboxManager from rock.sandbox.service.sandbox_proxy_service import SandboxProxyService @@ -208,3 +208,36 @@ def test_yaml_used_when_env_var_empty(self, sandbox_proxy_service): assert result["Endpoint"] == "yaml.endpoint" # YAML fallback assert result["Bucket"] == "yaml-bucket" assert result["Region"] == "rg" # env + + +class TestGetServerConfig: + @pytest.fixture + def proxy_service(self): + service = SandboxProxyService.__new__(SandboxProxyService) + service.server_config = ServerConfig( + image_registries=[ + ImageRegistryConfig(namespace="ns-1", registry_url="reg1.example.com", region="cn-hangzhou"), + ImageRegistryConfig(namespace="ns-2", registry_url="reg2.example.com", region="ap-southeast-1"), + ], + builder_image="builder:latest", + ) + return service + + def test_returns_public_config(self, proxy_service): + result = proxy_service.get_server_config() + + assert len(result["ImageRegistries"]) == 2 + assert result["ImageRegistries"][0]["Namespace"] == "ns-1" + assert result["ImageRegistries"][0]["RegistryUrl"] == "reg1.example.com" + assert result["ImageRegistries"][1]["Namespace"] == "ns-2" + assert result["ImageRegistries"][1]["Region"] == "ap-southeast-1" + assert result["BuilderImage"] == "builder:latest" + + def test_empty_registries(self): + service = SandboxProxyService.__new__(SandboxProxyService) + service.server_config = ServerConfig() + + result = service.get_server_config() + + assert result["ImageRegistries"] == [] + assert result["BuilderImage"] == ""