mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-23 18:15:08 +02:00
1afaca35a0
Adds `--image-uri <uri>` to `langgraph deploy` so self-hosted LangSmith customers can build, push, and deploy in one command without needing to script the three steps manually. When `--image-uri` is provided the CLI: - Builds the image tagged to the provided URI (same Docker build path as the local build flow) - Pushes using whatever Docker credentials are already in the environment (e.g. WIF, `aws ecr get-login-password`) — no auth handling in the CLI - PATCHes the deployment with `source_revision_config.image_uri` (no `revision_source`, which the self-hosted control plane rejects for `external_docker` deployments) Also fixes two self-hosted-specific issues uncovered during testing: - `LANGSMITH_ENDPOINT` is now used as a fallback when `LANGGRAPH_HOST_URL` isn't set — the CLI strips the `/api/v1` path and appends `/api-host` to reach the control plane - The httpx client now builds full URLs via string concatenation rather than relying on httpx base_url merging, which silently dropped the `/api-host` path prefix when paths started with `/` - The "Check status at" URL after a deploy now correctly points to the self-hosted LangSmith UI instead of `smith.langchain.com` **How did you verify your code works?** Tested end-to-end against a self-hosted LangSmith instance using ECR as the registry. `langgraph deploy --image-uri <ecr-uri>` successfully built, pushed, and triggered a deployment revision. Confirmed the existing SaaS flow (`langgraph deploy` without `--image-uri`) is unaffected — the new flag is opt-in and the `LANGSMITH_ENDPOINT` fallback only activates when `LANGGRAPH_HOST_URL` is unset and `LANGSMITH_ENDPOINT` is present. --------- Co-authored-by: Hari Dhanushkodi <hari-dhanushkodi@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Hugo Durand <hugo.durand@langchain.dev>
36 lines
1.1 KiB
Python
36 lines
1.1 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, replace
|
|
|
|
DIGEST_SEPARATOR = "@sha256:"
|
|
DIGEST_MARKER = "@"
|
|
TAG_SEPARATOR = ":"
|
|
PATH_SEPARATOR = "/"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ImageReference:
|
|
repository: str
|
|
tag: str | None = None
|
|
|
|
@classmethod
|
|
def parse(cls, reference: str) -> ImageReference:
|
|
if DIGEST_MARKER in reference:
|
|
raise ValueError(f"{reference!r} carries a digest and cannot be tagged")
|
|
path_start = reference.rfind(PATH_SEPARATOR) + 1
|
|
name, separator, tag = reference[path_start:].partition(TAG_SEPARATOR)
|
|
if not separator:
|
|
return cls(reference)
|
|
return cls(reference[:path_start] + name, tag)
|
|
|
|
def with_tag(self, tag: str) -> ImageReference:
|
|
return replace(self, tag=tag)
|
|
|
|
def matches_digest(self, repo_digest: str) -> bool:
|
|
return repo_digest.startswith(f"{self.repository}{DIGEST_SEPARATOR}")
|
|
|
|
def __str__(self) -> str:
|
|
if self.tag is None:
|
|
return self.repository
|
|
return f"{self.repository}{TAG_SEPARATOR}{self.tag}"
|