diff --git a/cmd/cli/agentcube/cli/main.py b/cmd/cli/agentcube/cli/main.py index 1defd012..1388d602 100644 --- a/cmd/cli/agentcube/cli/main.py +++ b/cmd/cli/agentcube/cli/main.py @@ -303,6 +303,11 @@ def publish( "--replicas", help="Number of replicas for K8s deployment (default: 1)", ), + endpoint: Optional[str] = typer.Option( + None, + "--endpoint", + help="Custom API endpoint for AgentCube or Kubernetes cluster", + ), namespace: Optional[str] = typer.Option( None, "--namespace", @@ -339,9 +344,10 @@ def publish( "description": description, "region": region, "cloud_provider": cloud_provider, - "provider": provider, # Pass provider down + "provider": provider, "node_port": node_port, "replicas": replicas, + "agent_endpoint": endpoint, "namespace": namespace, } diff --git a/cmd/cli/agentcube/runtime/publish_runtime.py b/cmd/cli/agentcube/runtime/publish_runtime.py index a427003a..9f327cb8 100644 --- a/cmd/cli/agentcube/runtime/publish_runtime.py +++ b/cmd/cli/agentcube/runtime/publish_runtime.py @@ -171,20 +171,20 @@ def _publish_cr_to_k8s( except Exception as e: raise RuntimeError(f"Failed to deploy AgentRuntime CR to K8s: {str(e)}") + endpoint = options.get('agent_endpoint') or metadata.agent_endpoint + if not endpoint: + raise ValueError("Please enter the endpoint for the agent") + # Step 4: Update metadata with K8s deployment information updates = { "agent_id": k8s_info["deployment_name"], + "agent_endpoint": endpoint, "k8s_deployment": { **k8s_info, "type": "AgentRuntime", } } - # Use provided endpoint or fall back to router_url from metadata - endpoint = options.get('agent_endpoint') or metadata.agent_endpoint - if not endpoint: - raise ValueError("Please enter the endpoint for the agent") - self.metadata_service.update_metadata(workspace_path, updates) result = { diff --git a/cmd/cli/tests/test_cli_publish.py b/cmd/cli/tests/test_cli_publish.py new file mode 100644 index 00000000..c3012081 --- /dev/null +++ b/cmd/cli/tests/test_cli_publish.py @@ -0,0 +1,55 @@ +# Copyright The Volcano Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typer.testing import CliRunner + +from agentcube.cli.main import app + + +def test_publish_endpoint_option_is_forwarded(monkeypatch, tmp_path): + call = {} + + class StubPublishRuntime: + def __init__(self, verbose=False, provider="agentcube"): + call["verbose"] = verbose + call["provider"] = provider + + def publish(self, workspace_path, **options): + call["workspace_path"] = workspace_path + call["options"] = options + return { + "agent_name": "test-agent", + "agent_id": "test-agent", + "agent_endpoint": options["agent_endpoint"], + "namespace": options.get("namespace", "default"), + "status": "deployed", + } + + monkeypatch.setattr("agentcube.cli.main.PublishRuntime", StubPublishRuntime) + + result = CliRunner().invoke( + app, + [ + "publish", + "-f", + str(tmp_path), + "--endpoint", + "http://router.example.com", + ], + ) + + assert result.exit_code == 0, result.output + assert call["provider"] == "agentcube" + assert call["workspace_path"] == tmp_path.resolve() + assert call["options"]["agent_endpoint"] == "http://router.example.com" diff --git a/cmd/cli/tests/test_env_support.py b/cmd/cli/tests/test_env_support.py index 832dc526..50de29a6 100644 --- a/cmd/cli/tests/test_env_support.py +++ b/cmd/cli/tests/test_env_support.py @@ -123,10 +123,10 @@ def test_save_without_env_omits_field(self): # PublishRuntime env forwarding tests # --------------------------------------------------------------------------- -class TestPublishRuntimeEnvForwarding: - """Test that env from metadata is forwarded to providers.""" +class TestPublishRuntimePublishOptions: + """Test publish options passed to providers and metadata.""" - def _make_metadata(self, env=None): + def _make_metadata(self, env=None, agent_endpoint="http://localhost:8080"): return AgentMetadata( agent_name="test-agent", entrypoint="python main.py", @@ -138,7 +138,7 @@ def _make_metadata(self, env=None): router_url="http://router:8080", readiness_probe_path="/health", readiness_probe_port=8080, - agent_endpoint="http://localhost:8080", + agent_endpoint=agent_endpoint, image={"repository_url": "registry.example.com/test:latest"}, env=env, ) @@ -259,3 +259,44 @@ def test_no_env_passes_none(self, MockMetaSvc, MockDockerSvc, MockACProvider): call_kwargs = mock_provider.deploy_agent_runtime.call_args passed_env = call_kwargs.kwargs.get("env_vars") or call_kwargs[1].get("env_vars") assert passed_env is None + + @patch("agentcube.runtime.publish_runtime.AgentCubeProvider") + @patch("agentcube.runtime.publish_runtime.DockerService") + @patch("agentcube.runtime.publish_runtime.MetadataService") + def test_agentcube_publish_saves_endpoint( + self, MockMetaSvc, MockDockerSvc, MockACProvider + ): + from agentcube.runtime.publish_runtime import PublishRuntime + + endpoint = "http://router.example.com" + meta = self._make_metadata(agent_endpoint=None) + + mock_meta_svc = MockMetaSvc.return_value + mock_meta_svc.load_metadata.return_value = meta + + mock_docker = MockDockerSvc.return_value + mock_docker.push_image.return_value = {"pushed_image": "registry.example.com/test:latest"} + + mock_provider = MagicMock() + mock_provider.deploy_agent_runtime.return_value = { + "deployment_name": "test-agent", + "namespace": "default", + "status": "deployed", + "type": "AgentRuntime", + } + MockACProvider.return_value = mock_provider + + runtime = PublishRuntime(verbose=False, provider="agentcube") + runtime.metadata_service = mock_meta_svc + runtime.docker_service = mock_docker + + with tempfile.TemporaryDirectory() as tmpdir: + result = runtime.publish( + Path(tmpdir), + provider="agentcube", + agent_endpoint=endpoint, + ) + + updates = mock_meta_svc.update_metadata.call_args.args[1] + assert updates["agent_endpoint"] == endpoint + assert result["agent_endpoint"] == endpoint