Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cmd/cli/agentcube/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this reads like a Kubernetes API server endpoint, but the value is stored as agent_endpoint and later used as the base URL for invoking the published agent. Can we make the help text say that more directly?

),
namespace: Optional[str] = typer.Option(
None,
"--namespace",
Expand Down Expand Up @@ -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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also persist this value in the AgentCube publish path? PublishRuntime._publish_cr_to_k8s returns agent_endpoint, but its metadata update only writes agent_id and k8s_deployment; a later agentcube invoke still reads metadata.agent_endpoint, so publishing with --endpoint can succeed and then invoke fails unless the metadata already had an endpoint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for the review and catching this @acsoto .
I updated the AgentCube publish path to persist the endpoint passed via --endpoint into metadata as agent_endpoint. I also added a runtime test to cover this path, so a later invoke can read the saved endpoint from metadata.

"namespace": namespace,
}

Expand Down
10 changes: 5 additions & 5 deletions cmd/cli/agentcube/runtime/publish_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we resolve and validate this before calling deploy_agent_runtime? If neither --endpoint nor metadata.agent_endpoint is set, publish currently creates the AgentRuntime CR and then fails before updating metadata, leaving the cluster state ahead of the workspace state.

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 = {
Expand Down
55 changes: 55 additions & 0 deletions cmd/cli/tests/test_cli_publish.py
Original file line number Diff line number Diff line change
@@ -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"
49 changes: 45 additions & 4 deletions cmd/cli/tests/test_env_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
)
Expand Down Expand Up @@ -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
Loading