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
30 changes: 30 additions & 0 deletions src/together/lib/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
JIG_VOLUMES_HELP_EXAMPLES,
EVALS_CREATE_HELP_EXAMPLES,
FILES_UPLOAD_HELP_EXAMPLES,
JIG_ROLLBACK_HELP_EXAMPLES,
BETA_CLUSTERS_HELP_EXAMPLES,
JIG_REVISIONS_HELP_EXAMPLES,
MODELS_UPLOAD_HELP_EXAMPLES,
JIG_JOB_STATUS_HELP_EXAMPLES,
JIG_SECRETS_SET_HELP_EXAMPLES,
Expand Down Expand Up @@ -582,6 +584,34 @@ async def run_command() -> None:
(f"{_CLI}.beta.jig.jig:queue_status_cli"), name="queue-status", help="Get queue metrics for the deployment"
)
jig_app.command((f"{_CLI}.beta.jig.jig:list_deployments_cli"), name="list", alias="ls", help="List all deployments")
jig_app.command(
(f"{_CLI}.beta.jig.jig:rollback_cli"),
name="rollback",
help="Roll back the deployment to a previous revision",
help_epilogue=JIG_ROLLBACK_HELP_EXAMPLES,
)

revisions_app = jig_app.command(
App(
name="revisions",
help="Inspect a deployment's revision history",
group="Subcommands",
help_epilogue=JIG_REVISIONS_HELP_EXAMPLES,
)
)
revisions_app.command(
(f"{_CLI}.beta.jig.jig:revisions_list_cli"),
name="list",
alias="ls",
help="List revision history (newest first)",
help_epilogue=JIG_REVISIONS_HELP_EXAMPLES,
)
revisions_app.command(
(f"{_CLI}.beta.jig.jig:revisions_get_cli"),
name="get",
alias=("retrieve", "describe"),
help="Get the full configuration of a specific revision",
)

secrets_app = jig_app.command(
App(name="secrets", help="Manage deployment secrets", group="Subcommands", help_epilogue=JIG_SECRETS_HELP_EXAMPLES)
Expand Down
132 changes: 132 additions & 0 deletions src/together/lib/cli/api/beta/jig/jig.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,22 @@ def logs(
return jig.follow_logs(replica_id, revision, version) if follow else jig.logs(replica_id, revision, version)


def rollback(jig: Jig, revision_identifier: str, detach: bool) -> Any:
"""Roll back the deployment to a previous revision, then track it back to ready"""
res = jig.together.post(
f"/deployments/{jig.name}/rollback",
body={"revision_identifier": revision_identifier},
cast_to=httpx.Response,
)
console.print(
f"\N{ANTICLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS} Rolling back {jig.name} to revision {revision_identifier}"
)
if detach:
return res
jig.track(jig.api.retrieve(jig.name))
return None


def destroy(jig: Jig) -> str:
"""Destroy deployment"""
jig.api.destroy(jig.name)
Expand All @@ -1250,6 +1266,74 @@ def list_deployments(jig: Jig) -> Any:
return jig.api.with_raw_response.list()


def get_revisions(jig: Jig, revision_id: str) -> Any:
"""Get the full configuration of a specific revision"""
return jig.together.get(
f"/deployments/{jig.name}/revisions/{revision_id}",
cast_to=httpx.Response,
)


def _fetch_revisions(jig: Jig, limit: int | None, before: int | None) -> httpx.Response:
params: dict[str, Any] = {}
if limit is not None:
params["limit"] = limit
if before:
params["before"] = before
return jig.together.get(
f"/deployments/{jig.name}/revisions",
options={"params": params},
cast_to=httpx.Response,
)


def _to_revision_list(jig: Jig, revisions: httpx.Response) -> None:
"""Render an already-fetched revision history response as a table (newest first)"""
body = revisions.json()
events: list[dict[str, Any]] = body.get("data") or []

table = ListTable(
title=f"Revisions for {jig.name}",
empty_message=f"No revisions found for deployment {jig.name}",
)
table.add_column("Event", ratio=None)
table.add_primary_column("Revision")
table.add_column("Action", ratio=None)
table.add_column("Activated", ratio=None)
table.add_column("Image", ratio=2)
table.add_column("Revision ID", ratio=2)

last_event = events[-1] if events else None
last_event_number = last_event.get("event_number") if last_event else None
has_more_events = last_event is not None and last_event.get("action") != "create"

for event in events:
event_number = event.get("event_number")
revision_number = event.get("revision_number")
image = event.get("image")
activated_at = event.get("activated_at")
time_since_activated = _age(activated_at)
table.add_row(
f"#{event_number}" if event_number is not None else "-",
f"#{revision_number}" if revision_number is not None else "-",
event.get("action") or "-",
f"{activated_at} [primary]({time_since_activated} ago)[/primary]",
jig.short_image(image) if image else "-",
event.get("revision_id") or "-",
)

console.print(table)

console.print("\n[dim]To see view the spec of a revision, run: [dim]")
console.print(f"[dim]-[/dim] [primary]tg beta jig revisions get <revision-number or revision-id> [/primary]")

if has_more_events:
console.print("\n[dim]To see older revisions for this deployment, run:[dim]")
console.print(f"[dim]-[/dim] [primary]tg beta jig revisions list --before {last_event_number}[/primary]\n")
else:
console.print("\n[muted]You've reached the end of the revision history for this deployment.[/muted]\n")


def secrets_set(jig: Jig, name: str, value: str, description: str) -> None:
"""Set a secret (create or update)"""
jig.set_secret(name, value, description)
Expand Down Expand Up @@ -1571,6 +1655,54 @@ def list_deployments_cli(
_run_jig_cmd(config, toml_config, list_deployments)


def revisions_list_cli(
limit: Annotated[
Optional[int], Parameter(name="--limit", help="Maximum number of events to return (default 20, max 100)")
] = None,
before: Annotated[
Optional[int],
Parameter(name="--before", help="Return events before this event number (exclusive, for pagination)"),
] = None,
*,
config: CLIConfigParameter,
toml_config: TomlConfigParameter = None,
) -> None:
"""List the deployment's revision history (newest first)."""

def inner(jig: Jig) -> Any:
revisions = _fetch_revisions(jig, limit, before)
if config.json:
return revisions
_to_revision_list(jig, revisions)
return None

_run_jig_cmd(config, toml_config, inner)


def revisions_get_cli(
revision_id: Annotated[str, Parameter(name="--revision-id", help="Revision UUID")],
*,
config: CLIConfigParameter,
toml_config: TomlConfigParameter = None,
) -> None:
"""Get the full configuration of a specific revision."""
_run_jig_cmd(config, toml_config, lambda jig: get_revisions(jig, revision_id))


def rollback_cli(
revision: Annotated[str, Parameter(name="--revision", help="Revision number or UUID to roll back to")],
detach: Annotated[
bool,
Parameter(help="Return immediately without waiting for the rollback to complete", negative=()),
] = False,
*,
config: CLIConfigParameter,
toml_config: TomlConfigParameter = None,
) -> None:
"""Roll back the deployment to a previous revision."""
_run_jig_cmd(config, toml_config, lambda jig: rollback(jig, revision, detach))


def secrets_set_cli(
name: Annotated[str, Parameter(name="--name", help="Secret name")],
value: Annotated[str, Parameter(name="--value", help="Secret value")],
Expand Down
2 changes: 1 addition & 1 deletion src/together/lib/cli/utils/_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"prompt.choices": "#caaef5", # Purple 300 ⭐
"prompt.default": "dim #98a0b3", # Grey 400 ⭐
# Table styles
"table.header": "#414858", # Purple 300 ⭐ (lighter when bold)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

this color code was incorrect (the comment indicates purple, which is consistent with the theming, but the color code was gray).

"table.header": "#caaef5", # Purple 300 ⭐ (lighter when bold)
"table.border": "#626b84", # Grey 600 ⭐
"table.row": "#c4c9d4", # Grey 300 ⭐
# Progress/Loading
Expand Down
25 changes: 25 additions & 0 deletions src/together/lib/cli/utils/_help_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@
[dim]-[/dim] List deployments or tear one down:
[primary]tg beta jig list[/primary]
[primary]tg beta jig destroy[/primary]

[dim]-[/dim] List revision history:
[primary]tg beta jig revisions list[/primary]
"""

JIG_SECRETS_HELP_EXAMPLES = """[dim]Examples:[/dim]
Expand Down Expand Up @@ -466,6 +469,28 @@
[primary]tg beta jig destroy[/primary]
"""

JIG_REVISIONS_HELP_EXAMPLES = """[dim]Examples:[/dim]
[dim]-[/dim] Show revision history (newest first, up to 10 events):
[primary]tg beta jig revisions list[/primary]

[dim]-[/dim] View a page of 5 revision events, event numbers 5 to 10 (if present):
[primary]tg beta jig revisions list --limit 5 --before 11 [/primary]

[dim]-[/dim] Inspect the full configuration of one revision (by revision number or revision ID):
[primary]tg beta jig revisions get 4 [/primary]
"""

JIG_ROLLBACK_HELP_EXAMPLES = """[dim]Examples:[/dim]
[dim]-[/dim] Roll back to a revision by number (from [primary]revisions list[/primary]):
[primary]tg beta jig rollback 7[/primary]

[dim]-[/dim] Roll back to a revision by UUID:
[primary]tg beta jig rollback 2accdd2a-9094-4682-9016-06e4f14daaff[/primary]

[dim]-[/dim] Start the rollback and return immediately, without tracking:
[primary]tg beta jig rollback 7 --detach[/primary]
"""

JIG_LOGS_HELP_EXAMPLES = """[dim]Examples:[/dim]
[dim]-[/dim] Print recent logs once:
[primary]tg beta jig logs[/primary]
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading