mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-08 18:57:52 +02:00
Add delete-deployment command.
This commit is contained in:
@@ -1008,6 +1008,80 @@ def deploy(
|
||||
)
|
||||
|
||||
|
||||
@click.option(
|
||||
"--api-key",
|
||||
envvar="LANGGRAPH_HOST_API_KEY",
|
||||
help=(
|
||||
"API key. Can also be set via LANGGRAPH_HOST_API_KEY, "
|
||||
"LANGSMITH_API_KEY, or LANGCHAIN_API_KEY environment variable or .env file."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--host-url",
|
||||
envvar="LANGGRAPH_HOST_URL",
|
||||
default="https://api.host.langchain.com",
|
||||
hidden=True,
|
||||
)
|
||||
@click.option(
|
||||
"--force",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Delete the deployment without prompting for confirmation.",
|
||||
)
|
||||
@click.argument("deployment_id")
|
||||
@cli.command(
|
||||
help=(
|
||||
"[Beta] Delete a LangSmith Deployment.\n\n"
|
||||
"This command is in beta and under active development."
|
||||
)
|
||||
)
|
||||
@log_command
|
||||
def delete_deployment(
|
||||
deployment_id: str,
|
||||
host_url: str | None,
|
||||
api_key: str | None,
|
||||
force: bool,
|
||||
) -> None:
|
||||
click.secho(
|
||||
"Note: 'langgraph delete-deployment' is in beta. Expect frequent updates and improvements.",
|
||||
fg="yellow",
|
||||
)
|
||||
click.echo()
|
||||
|
||||
if not api_key:
|
||||
api_key = click.prompt("Host API key", hide_input=True)
|
||||
|
||||
if not force:
|
||||
confirmation = click.prompt(
|
||||
f"Are you sure you want to delete deployment ID {deployment_id} (Y/n)?",
|
||||
default="N",
|
||||
show_default=False,
|
||||
)
|
||||
if confirmation.strip() != "Y":
|
||||
raise click.ClickException("Deployment not deleted.")
|
||||
|
||||
client = HostBackendClient(host_url, api_key)
|
||||
try:
|
||||
client.delete_deployment(deployment_id)
|
||||
except HostBackendError as err:
|
||||
if err.status_code == 403 and "requires workspace specification" in err.message:
|
||||
click.secho(
|
||||
"Your API key is org-scoped and requires a workspace ID.",
|
||||
fg="yellow",
|
||||
)
|
||||
click.secho(
|
||||
"Find your workspace ID in LangSmith under Settings > Workspaces.",
|
||||
fg="yellow",
|
||||
)
|
||||
tenant_id = click.prompt("Workspace ID")
|
||||
client = HostBackendClient(host_url, api_key, tenant_id=tenant_id)
|
||||
client.delete_deployment(deployment_id)
|
||||
else:
|
||||
raise
|
||||
|
||||
click.secho(f"Deleted deployment '{deployment_id}'.", fg="green")
|
||||
|
||||
|
||||
def _normalize_image_name(value: str | None) -> str:
|
||||
"""Sanitize a deployment/directory name into a valid Docker repository name.
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ class HostBackendClient:
|
||||
def get_deployment(self, deployment_id: str) -> dict[str, Any]:
|
||||
return self._request("GET", f"/v2/deployments/{deployment_id}")
|
||||
|
||||
def delete_deployment(self, deployment_id: str) -> None:
|
||||
self._request("DELETE", f"/v2/deployments/{deployment_id}")
|
||||
|
||||
def request_push_token(self, deployment_id: str) -> dict[str, Any]:
|
||||
return self._request(
|
||||
"POST",
|
||||
|
||||
@@ -7,6 +7,7 @@ import textwrap
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from langgraph_cli.cli import cli, prepare_args_and_stdin
|
||||
@@ -287,6 +288,97 @@ def test_version_option() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_delete_deployment_command_calls_backend(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
calls: list[tuple[str | None, str, str | None]] = []
|
||||
|
||||
class FakeHostBackendClient:
|
||||
def __init__(
|
||||
self, base_url: str | None, api_key: str, tenant_id: str | None = None
|
||||
) -> None:
|
||||
calls.append((base_url, api_key, tenant_id))
|
||||
|
||||
def delete_deployment(self, deployment_id: str) -> None:
|
||||
calls.append(("delete", deployment_id, None))
|
||||
|
||||
monkeypatch.setattr("langgraph_cli.cli.HostBackendClient", FakeHostBackendClient)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
["delete-deployment", "--api-key", "test-key", "dep-123"],
|
||||
input="Y\n",
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert calls == [
|
||||
("https://api.host.langchain.com", "test-key", None),
|
||||
("delete", "dep-123", None),
|
||||
]
|
||||
assert "Deleted deployment 'dep-123'." in result.output
|
||||
|
||||
|
||||
def test_delete_deployment_command_requires_y(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
called = False
|
||||
|
||||
class FakeHostBackendClient:
|
||||
def __init__(
|
||||
self, base_url: str | None, api_key: str, tenant_id: str | None = None
|
||||
) -> None:
|
||||
nonlocal called
|
||||
called = True
|
||||
|
||||
def delete_deployment(self, deployment_id: str) -> None:
|
||||
nonlocal called
|
||||
called = True
|
||||
|
||||
monkeypatch.setattr("langgraph_cli.cli.HostBackendClient", FakeHostBackendClient)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
["delete-deployment", "--api-key", "test-key", "dep-123"],
|
||||
input="N\n",
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "Deployment not deleted." in result.output
|
||||
assert called is False
|
||||
|
||||
|
||||
def test_delete_deployment_command_force_skips_confirmation(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
calls: list[tuple[str | None, str, str | None]] = []
|
||||
|
||||
class FakeHostBackendClient:
|
||||
def __init__(
|
||||
self, base_url: str | None, api_key: str, tenant_id: str | None = None
|
||||
) -> None:
|
||||
calls.append((base_url, api_key, tenant_id))
|
||||
|
||||
def delete_deployment(self, deployment_id: str) -> None:
|
||||
calls.append(("delete", deployment_id, None))
|
||||
|
||||
monkeypatch.setattr("langgraph_cli.cli.HostBackendClient", FakeHostBackendClient)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
["delete-deployment", "--api-key", "test-key", "--force", "dep-123"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "Type Y to delete deployment" not in result.output
|
||||
assert calls == [
|
||||
("https://api.host.langchain.com", "test-key", None),
|
||||
("delete", "dep-123", None),
|
||||
]
|
||||
|
||||
|
||||
def test_dockerfile_command_basic() -> None:
|
||||
"""Test the 'dockerfile' command with basic configuration."""
|
||||
runner = CliRunner()
|
||||
|
||||
@@ -130,6 +130,10 @@ def test_get_deployment(client):
|
||||
assert result == {"ok": True}
|
||||
|
||||
|
||||
def test_delete_deployment(client):
|
||||
assert client.delete_deployment("dep-123") is None
|
||||
|
||||
|
||||
def test_list_deployments(client):
|
||||
result = client.list_deployments("my-app")
|
||||
assert result == {"ok": True}
|
||||
|
||||
Reference in New Issue
Block a user