This commit is contained in:
hari-dhanushkodi
2026-03-09 12:10:55 -04:00
parent fac583d833
commit c8a0bd4dea
3 changed files with 44 additions and 35 deletions
+25 -7
View File
@@ -61,6 +61,7 @@ RESERVED_ENV_VARS = frozenset(
"LANGCHAIN_CALLBACKS_BACKGROUND",
"DD_TRACE_PSYCOPG_ENABLED",
"DD_TRACE_REDIS_ENABLED",
"LANGSMITH_DEPLOYMENT_NAME",
"LANGGRAPH_CLOUD_LICENSE_KEY",
# ALLOWED_SELF_HOSTED_ENV_VARS (rejected for non-self-hosted)
"LANGSMITH_API_KEY",
@@ -83,6 +84,8 @@ _API_KEY_ENV_NAMES = (
"LANGCHAIN_API_KEY",
)
_DEPLOYMENT_NAME_ENV = "LANGSMITH_DEPLOYMENT_NAME"
def _parse_dotenv_file(path: pathlib.Path) -> dict[str, str]:
"""Parse a .env file into a dict, skipping comments and blank lines."""
@@ -452,6 +455,7 @@ def _build(
extra_flags: Sequence[str] = (),
verbose: bool = True,
):
# pull latest images
if pull:
runner.run(
subp_exec(
@@ -462,15 +466,19 @@ def _build(
)
)
set("Building...")
# apply options
args = [
"-f",
"-", # stdin
"-t",
tag,
]
# determine build context: use current directory for JS projects, config parent for Python
is_js_project = config_json.get("node_version") and not config_json.get(
"python_version"
)
# build/install commands only apply to JS projects for now
# without install/build command, JS projects will follow the old behavior
if is_js_project and (build_command or install_command):
build_context = str(pathlib.Path.cwd())
else:
@@ -488,6 +496,7 @@ def _build(
build_command=build_command,
build_context=build_context,
)
# add additional_contexts
if additional_contexts:
for k, v in additional_contexts.items():
args.extend(["--build-context", f"{k}={v}"])
@@ -588,12 +597,16 @@ def build(
@click.option(
"--api-key",
envvar="LANGGRAPH_HOST_API_KEY",
help="API key. If omitted, resolved from .env or prompted.",
help=(
"API key. Can also be set via LANGGRAPH_HOST_API_KEY, "
"LANGSMITH_API_KEY, or LANGCHAIN_API_KEY in your .env file."
),
)
@click.option(
"--name",
help=(
"Deployment name. Defaults to current directory name "
"Deployment name. Can also be set via LANGSMITH_DEPLOYMENT_NAME "
"in your .env file. Defaults to current directory name "
"if --deployment-id is not provided."
),
)
@@ -686,6 +699,8 @@ def deploy(
if not api_key:
api_key = click.prompt("Host API key", hide_input=True)
if not deployment_id and not name:
name = env_vars.get(_DEPLOYMENT_NAME_ENV)
if not deployment_id and not name:
default_name = _normalize_image_name(pathlib.Path.cwd().name)
name = click.prompt("Deployment name", default=default_name)
@@ -698,11 +713,13 @@ def deploy(
local_tag = f"langgraph-deploy-tmp:{int(time.time())}"
with Runner() as runner:
langgraph_cli.docker.check_capabilities(
runner,
require_compose=False,
require_buildx=needs_buildx,
)
if shutil.which("docker") is None:
raise click.UsageError("Docker not installed")
if needs_buildx:
try:
runner.run(subp_exec("docker", "buildx", "version", collect=True))
except click.exceptions.Exit:
raise click.UsageError("Docker Buildx not installed") from None
def log_step(message: str) -> None:
click.secho(message, fg="cyan")
@@ -896,6 +913,7 @@ def deploy(
)
if status != last_status:
last_status = status
# pause spinner so we can avoid conflict when writing status
set_progress("")
click.secho(f" Status: {status}", fg="cyan")
if status in _TERMINAL_STATUSES:
+18 -28
View File
@@ -24,9 +24,9 @@ DockerComposeType = Literal["plugin", "standalone"]
class DockerCapabilities(NamedTuple):
version_docker: Version
version_compose: Version | None
version_compose: Version
healthcheck_start_interval: bool
compose_type: DockerComposeType | None = None
compose_type: DockerComposeType = "plugin"
def _parse_version(version: str) -> Version:
@@ -45,9 +45,7 @@ def _parse_version(version: str) -> Version:
)
def check_capabilities(
runner, *, require_compose: bool = True, require_buildx: bool = False
) -> DockerCapabilities:
def check_capabilities(runner) -> DockerCapabilities:
# check docker available
if shutil.which("docker") is None:
raise click.UsageError("Docker not installed") from None
@@ -63,33 +61,25 @@ def check_capabilities(
if not info["ServerVersion"]:
raise click.UsageError("Docker not running") from None
compose_type: DockerComposeType | None = None
compose_version: Version | None = None
if require_compose:
try:
compose = next(
p for p in info["ClientInfo"]["Plugins"] if p["Name"] == "compose"
)
compose_version_str = compose["Version"]
compose_type = "plugin"
except (KeyError, StopIteration):
if shutil.which("docker-compose") is None:
raise click.UsageError("Docker Compose not installed") from None
compose_type: DockerComposeType
try:
compose = next(
p for p in info["ClientInfo"]["Plugins"] if p["Name"] == "compose"
)
compose_version_str = compose["Version"]
compose_type = "plugin"
except (KeyError, StopIteration):
if shutil.which("docker-compose") is None:
raise click.UsageError("Docker Compose not installed") from None
compose_version_str, _ = runner.run(
subp_exec("docker-compose", "--version", "--short", collect=True)
)
compose_type = "standalone"
compose_version = _parse_version(compose_version_str)
if require_buildx:
try:
runner.run(subp_exec("docker", "buildx", "version", collect=True))
except click.exceptions.Exit:
raise click.UsageError("Docker Buildx not installed") from None
compose_version_str, _ = runner.run(
subp_exec("docker-compose", "--version", "--short", collect=True)
)
compose_type = "standalone"
# parse versions
docker_version = _parse_version(info["ServerVersion"])
compose_version = _parse_version(compose_version_str)
# check capabilities
return DockerCapabilities(
+1
View File
@@ -24,6 +24,7 @@ class Progress:
sys.stdout.write(next(self.spinner_generator) + " " + message)
sys.stdout.flush()
time.sleep(self.delay)
# clear the spinner and message
sys.stdout.write(
"\b" * (len(message) + 2)
+ " " * (len(message) + 2)