lightning-sdk 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/ai_hub_api.py +1 -0
  3. lightning_sdk/api/lit_container_api.py +84 -24
  4. lightning_sdk/api/teamspace_api.py +8 -9
  5. lightning_sdk/api/utils.py +0 -1
  6. lightning_sdk/cli/docker.py +1 -1
  7. lightning_sdk/cli/download.py +10 -2
  8. lightning_sdk/cli/serve.py +26 -19
  9. lightning_sdk/cli/upload.py +41 -6
  10. lightning_sdk/lightning_cloud/openapi/__init__.py +16 -0
  11. lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +9 -9
  12. lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +202 -0
  13. lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +105 -0
  14. lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +15 -3
  15. lightning_sdk/lightning_cloud/openapi/models/__init__.py +16 -0
  16. lightning_sdk/lightning_cloud/openapi/models/alerts_config_billing.py +175 -0
  17. lightning_sdk/lightning_cloud/openapi/models/alerts_config_studios.py +149 -0
  18. lightning_sdk/lightning_cloud/openapi/models/environmenttemplates_id_body.py +27 -1
  19. lightning_sdk/lightning_cloud/openapi/models/externalv1_cloud_space_instance_status.py +27 -1
  20. lightning_sdk/lightning_cloud/openapi/models/externalv1_cluster.py +27 -1
  21. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +53 -1
  22. lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py +27 -1
  23. lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +27 -1
  24. lightning_sdk/lightning_cloud/openapi/models/server_id_alerts_body.py +201 -0
  25. lightning_sdk/lightning_cloud/openapi/models/v1_alert_method.py +102 -0
  26. lightning_sdk/lightning_cloud/openapi/models/v1_alerts_config.py +149 -0
  27. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_cold_start_metrics.py +617 -0
  28. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +27 -1
  29. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template_config.py +123 -0
  30. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
  31. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +131 -1
  32. lightning_sdk/lightning_cloud/openapi/models/v1_conversation_response_chunk.py +29 -3
  33. lightning_sdk/lightning_cloud/openapi/models/v1_create_cloud_space_environment_template_request.py +27 -1
  34. lightning_sdk/lightning_cloud/openapi/models/v1_create_project_request.py +27 -1
  35. lightning_sdk/lightning_cloud/openapi/models/v1_create_server_alert_response.py +97 -0
  36. lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
  37. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +81 -3
  38. lightning_sdk/lightning_cloud/openapi/models/v1_lambda_labs_direct_v1.py +1 -29
  39. lightning_sdk/lightning_cloud/openapi/models/v1_list_cloud_space_cold_start_metrics_response.py +123 -0
  40. lightning_sdk/lightning_cloud/openapi/models/v1_list_lit_registry_repository_image_artifact_versions_response.py +27 -1
  41. lightning_sdk/lightning_cloud/openapi/models/v1_message.py +29 -3
  42. lightning_sdk/lightning_cloud/openapi/models/v1_metadata.py +27 -1
  43. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +53 -1
  44. lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +27 -1
  45. lightning_sdk/lightning_cloud/openapi/models/v1_report_cloud_space_instance_stop_at_response.py +97 -0
  46. lightning_sdk/lightning_cloud/openapi/models/v1_reservation_details.py +201 -0
  47. lightning_sdk/lightning_cloud/openapi/models/v1_rule_resource.py +1 -0
  48. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert.py +201 -0
  49. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_phase.py +104 -0
  50. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_severity.py +103 -0
  51. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_type.py +105 -0
  52. lightning_sdk/lightning_cloud/openapi/models/v1_subnet_spec.py +149 -0
  53. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +131 -79
  54. lightning_sdk/lightning_cloud/openapi/models/v1_voltage_park_direct_v1.py +29 -29
  55. lightning_sdk/lightning_cloud/openapi/models/v1_vultr_direct_v1.py +1 -27
  56. lightning_sdk/lit_container.py +19 -4
  57. lightning_sdk/models.py +1 -1
  58. lightning_sdk/serve.py +86 -15
  59. lightning_sdk/teamspace.py +32 -18
  60. {lightning_sdk-0.2.5.dist-info → lightning_sdk-0.2.7.dist-info}/METADATA +1 -1
  61. {lightning_sdk-0.2.5.dist-info → lightning_sdk-0.2.7.dist-info}/RECORD +65 -49
  62. {lightning_sdk-0.2.5.dist-info → lightning_sdk-0.2.7.dist-info}/LICENSE +0 -0
  63. {lightning_sdk-0.2.5.dist-info → lightning_sdk-0.2.7.dist-info}/WHEEL +0 -0
  64. {lightning_sdk-0.2.5.dist-info → lightning_sdk-0.2.7.dist-info}/entry_points.txt +0 -0
  65. {lightning_sdk-0.2.5.dist-info → lightning_sdk-0.2.7.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py CHANGED
@@ -31,6 +31,6 @@ __all__ = [
31
31
  "User",
32
32
  ]
33
33
 
34
- __version__ = "0.2.5"
34
+ __version__ = "0.2.7"
35
35
  _check_version_and_prompt_upgrade(__version__)
36
36
  _set_tqdm_envvars_noninteractive()
@@ -165,6 +165,7 @@ class AIHubApi:
165
165
  name=name,
166
166
  replicas=1,
167
167
  spec=template.spec_v2.job,
168
+ parent_template_id=template_id,
168
169
  ),
169
170
  )
170
171
 
@@ -1,9 +1,10 @@
1
1
  import inspect
2
2
  import time
3
- from typing import Any, Callable, Dict, Generator, Iterator, List
3
+ from typing import Any, Callable, Dict, Generator, Iterator, List, Optional
4
4
 
5
5
  import docker
6
6
  import requests
7
+ from rich.console import Console
7
8
 
8
9
  from lightning_sdk.api.utils import _get_registry_url
9
10
  from lightning_sdk.lightning_cloud.env import LIGHTNING_CLOUD_URL
@@ -21,6 +22,21 @@ class DockerPushError(Exception):
21
22
  pass
22
23
 
23
24
 
25
+ class DockerNotRunningError(Exception):
26
+ def __init__(self, message: str = "Failed to connect to Docker") -> None:
27
+ self.message = message
28
+ super().__init__(self.message)
29
+
30
+ def print_help(self) -> None:
31
+ console = Console()
32
+ console.print("[bold red]Docker Error: Failed to connect to Docker. Is it running?[/bold red]")
33
+ console.print("[yellow]Troubleshooting:[/yellow]")
34
+ console.print("1. Check if Docker daemon is running")
35
+ console.print("2. Verify your user has proper permissions")
36
+ console.print("3. Try restarting Docker service")
37
+ console.print("4. Read more here: https://docs.docker.com/engine/daemon/start/")
38
+
39
+
24
40
  def retry_on_lcr_auth_failure(func: Callable) -> Callable:
25
41
  def generator_wrapper(self: "LitContainerApi", *args: Any, **kwargs: Any) -> Callable:
26
42
  try:
@@ -54,9 +70,7 @@ class LitContainerApi:
54
70
  self._docker_client.ping()
55
71
  self._docker_auth_config = {}
56
72
  except docker.errors.DockerException:
57
- raise RuntimeError(
58
- "Failed to connect to Docker, follow these steps to start it: https://docs.docker.com/engine/daemon/start/"
59
- ) from None
73
+ raise DockerNotRunningError() from None
60
74
 
61
75
  def authenticate(self, reauth: bool = False) -> bool:
62
76
  resp = None
@@ -85,45 +99,79 @@ class LitContainerApi:
85
99
  print(f"Authentication error: {e} resp: {resp}")
86
100
  return False
87
101
 
88
- def list_containers(self, project_id: str) -> List:
89
- project = self._client.lit_registry_service_get_lit_project_registry(project_id)
102
+ def list_containers(self, project_id: str, cloud_account: Optional[str] = None) -> List:
103
+ """Lists containers of the project ID.
104
+
105
+ :param project_id: The non-human readable project ID used internally to identify projects.
106
+ :return:
107
+ """
108
+ project = self._client.lit_registry_service_get_lit_project_registry(
109
+ project_id, cluster_id=cloud_account
110
+ ) # cloud account on the CLI is cluster_id
90
111
  return project.repositories
91
112
 
92
113
  def delete_container(self, project_id: str, container: str) -> V1DeleteLitRepositoryResponse:
114
+ """Deletes the container from LitCR. Garbage collection will come in and do the final sweep of the blob.
115
+
116
+ :param project_id: The non-human readable project ID used internally to identify projects.
117
+ :param container: The name of the docker container a user wants to push up, ie, nginx, vllm, etc
118
+ :return:
119
+ """
93
120
  try:
94
121
  return self._client.lit_registry_service_delete_lit_repository(project_id, container)
95
122
  except Exception as e:
96
123
  raise ValueError(f"Could not delete container {container} from project {project_id}: {e!s}") from e
97
124
 
98
125
  @retry_on_lcr_auth_failure
99
- def upload_container(self, container: str, teamspace: Teamspace, tag: str) -> Generator[dict, None, None]:
126
+ def upload_container(
127
+ self, container: str, teamspace: Teamspace, tag: str, cloud_account: str, platform: str
128
+ ) -> Generator[dict, None, None]:
129
+ """Upload container will push the container to LitCR.
130
+
131
+ It uses docker push API to interact with docker daemon which will then push the container to a storage
132
+ location defined by teamspace + cloud_account. Will retry pushes if not authenticated or if push errors happen
133
+
134
+ :param container: The name of the docker container a user wants to push up, ie, nginx, vllm, etc
135
+ :param teamspace: The teamspace he container is going to appear in. This is <OWNER_ID>/<TEAMSPACE_NAME>
136
+ :param tag: The container tag, default will latest.
137
+ :param cloud_account: If empty will be pushed to Lightning SaaS storage. Otherwise, this will be cluster_id.
138
+ Named cloud-account in the CLI options.
139
+ :param platform: If empty will be linux/amd64. This is important because our entire deployment infra runs on
140
+ linux/amd64. Will show user a warning otherwise.
141
+ :return: Generator[dict, None, None]
142
+ """
100
143
  try:
101
- self._docker_client.images.get(container)
144
+ self._docker_client.images.get(f"{container}:{tag}")
102
145
  except docker.errors.ImageNotFound:
103
146
  try:
104
- self._docker_client.images.pull(container, tag)
105
- self._docker_client.images.get(container)
147
+ self._docker_client.images.pull(repository=container, tag=tag, platform=platform)
148
+ self._docker_client.images.get(f"{container}:{tag}")
149
+ except docker.errors.ImageNotFound as e:
150
+ raise ValueError(f"Container {container}:{tag} does not exist") from e
106
151
  except docker.errors.APIError as e:
107
152
  raise ValueError(f"Could not pull container {container}") from e
108
- except docker.errors.ImageNotFound as e:
109
- raise ValueError(f"Container {container} does not exist") from e
110
153
  except Exception as e:
111
- raise ValueError(f"Unable to upload {container}") from e
154
+ raise ValueError(f"Unable to upload {container}:{tag}") from e
112
155
 
113
156
  registry_url = _get_registry_url()
114
157
  container_basename = container.split("/")[-1]
115
- repository = f"{registry_url}/lit-container/{teamspace.owner.name}/{teamspace.name}/{container_basename}"
116
- tagged = self._docker_client.api.tag(container, repository, tag)
158
+ repository = (
159
+ f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
160
+ f"{teamspace.owner.name}/{teamspace.name}/{container_basename}"
161
+ )
162
+ tagged = self._docker_client.api.tag(f"{container}:{tag}", repository, tag)
117
163
  if not tagged:
118
- raise ValueError(f"Could not tag container {container} with {repository}:{tag}")
119
- yield from self._push_with_retry(repository)
164
+ raise ValueError(f"Could not tag container {container}:{tag} with {repository}:{tag}")
165
+ yield from self._push_with_retry(repository, tag=tag)
166
+
120
167
  yield {
121
168
  "finish": True,
122
- "url": f"{LIGHTNING_CLOUD_URL}/{teamspace.owner.name}/{teamspace.name}/containers/{container_basename}",
169
+ "url": f"{LIGHTNING_CLOUD_URL}/{teamspace.owner.name}/{teamspace.name}/containers/{container_basename}"
170
+ f"{f'?clusterId={cloud_account}' if cloud_account is not None else ''}",
123
171
  "repository": repository,
124
172
  }
125
173
 
126
- def _push_with_retry(self, repository: str, max_retries: int = 3) -> Iterator[Dict[str, Any]]:
174
+ def _push_with_retry(self, repository: str, tag: str, max_retries: int = 3) -> Iterator[Dict[str, Any]]:
127
175
  def is_auth_error(error_msg: str) -> bool:
128
176
  auth_errors = ["unauthorized", "authentication required", "unauth"]
129
177
  return any(err in error_msg.lower() for err in auth_errors)
@@ -141,7 +189,7 @@ class LitContainerApi:
141
189
  time.sleep(2)
142
190
 
143
191
  lines = self._docker_client.api.push(
144
- repository, stream=True, decode=True, auth_config=self._docker_auth_config
192
+ repository, tag=tag, stream=True, decode=True, auth_config=self._docker_auth_config
145
193
  )
146
194
  for line in lines:
147
195
  if isinstance(line, dict) and "error" in line:
@@ -149,7 +197,7 @@ class LitContainerApi:
149
197
  if is_auth_error(error) or is_timeout_error(error):
150
198
  if attempt < max_retries - 1:
151
199
  break
152
- raise DockerPushError(f"Max retries reached: {error}")
200
+ raise DockerPushError(f"Max retries reached while pushing: {error}")
153
201
  raise DockerPushError(f"Push error: {error}")
154
202
  yield line
155
203
  else:
@@ -158,14 +206,26 @@ class LitContainerApi:
158
206
  except docker.errors.APIError as e:
159
207
  if (is_auth_error(str(e)) or is_timeout_error(str(e))) and attempt < max_retries - 1:
160
208
  continue
161
- raise DockerPushError(f"Push failed: {e}") from e
209
+ raise DockerPushError("Pushing the container failed") from e
162
210
 
163
211
  raise DockerPushError("Max push retries reached")
164
212
 
165
213
  @retry_on_lcr_auth_failure
166
- def download_container(self, container: str, teamspace: Teamspace, tag: str) -> Generator[str, None, None]:
214
+ def download_container(
215
+ self, container: str, teamspace: Teamspace, tag: str, cloud_account: Optional[str] = None
216
+ ) -> Generator[str, None, None]:
217
+ """Will download container from LitCR. Optionally from a BYOC cluster.
218
+
219
+ :param container: The name of the container to download
220
+ :param teamspace: The teamspace containing the container
221
+ :param tag: The tag of the container to download
222
+ :return: Generator yielding download status
223
+ """
167
224
  registry_url = _get_registry_url()
168
- repository = f"{registry_url}/lit-container/{teamspace.owner.name}/{teamspace.name}/{container}"
225
+ repository = (
226
+ f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
227
+ f"{teamspace.owner.name}/{teamspace.name}/{container}"
228
+ )
169
229
  try:
170
230
  self._docker_client.images.pull(repository, tag=tag, auth_config=self._docker_auth_config)
171
231
  except requests.exceptions.HTTPError as e:
@@ -215,16 +215,15 @@ class TeamspaceApi:
215
215
  version: str,
216
216
  local_path: Path,
217
217
  remote_path: str,
218
- cloud_account: str,
219
218
  teamspace_id: str,
220
219
  progress_bar: bool = True,
221
220
  ) -> None:
221
+ """Upload a file to the model store."""
222
222
  uploader = _ModelFileUploader(
223
223
  client=self._client,
224
224
  model_id=model_id,
225
225
  version=version,
226
226
  teamspace_id=teamspace_id,
227
- cloud_account=cloud_account,
228
227
  file_path=str(local_path),
229
228
  remote_path=str(remote_path),
230
229
  progress_bar=progress_bar,
@@ -235,20 +234,20 @@ class TeamspaceApi:
235
234
  self,
236
235
  model_id: str,
237
236
  version: str,
238
- root_path: Path,
239
- filepaths: List[Path],
240
- cloud_account: str,
237
+ file_paths: List[Path],
238
+ remote_paths: List[str],
241
239
  teamspace_id: str,
242
240
  progress_bar: bool = True,
243
241
  ) -> None:
244
- main_pbar = tqdm(total=len(filepaths), desc="Uploading files...", position=0) if progress_bar else None
245
- for filepath in filepaths:
242
+ """Upload files to the model store."""
243
+ main_pbar = tqdm(total=len(file_paths), desc="Uploading files...", position=0) if progress_bar else None
244
+ assert len(file_paths) == len(remote_paths), "File paths and remote paths must have the same length"
245
+ for filepath, remote_path in zip(file_paths, remote_paths):
246
246
  self.upload_model_file(
247
247
  model_id=model_id,
248
248
  version=version,
249
249
  local_path=filepath,
250
- remote_path=str(filepath.relative_to(root_path)),
251
- cloud_account=cloud_account,
250
+ remote_path=remote_path,
252
251
  teamspace_id=teamspace_id,
253
252
  progress_bar=progress_bar, # TODO: Global progress bar
254
253
  )
@@ -202,7 +202,6 @@ class _ModelFileUploader:
202
202
  model_id: str,
203
203
  version: str,
204
204
  teamspace_id: str,
205
- cloud_account: str,
206
205
  file_path: str,
207
206
  remote_path: str,
208
207
  progress_bar: bool,
@@ -19,4 +19,4 @@ def api(server_filename: str, port: int = 8000, gpu: bool = False, tag: str = "l
19
19
 
20
20
 
21
21
  def _api(server_filename: str, port: int = 8000, gpu: bool = False, tag: str = "litserve-model") -> str:
22
- return _LitServeDeployer().dockerize_api(server_filename=server_filename, port=port, gpu=gpu, tag=tag)
22
+ return _LitServeDeployer(None, None).dockerize_api(server_filename=server_filename, port=port, gpu=gpu, tag=tag)
@@ -143,7 +143,15 @@ def file(path: str = "", studio: Optional[str] = None, local_path: str = ".") ->
143
143
  @click.argument("container")
144
144
  @click.option("--teamspace", default=None, help="The name of the teamspace to download the container from")
145
145
  @click.option("--tag", default="latest", show_default=True, help="The tag of the container to download.")
146
- def download_container(container: str, teamspace: Optional[str] = None, tag: str = "latest") -> None:
146
+ @click.option(
147
+ "--cloud-account",
148
+ "--cloud_account", # The UI will present the above variant, using this as a secondary to be consistent w/ models
149
+ default=None,
150
+ help="The name of the cloud account to download the Container from.",
151
+ )
152
+ def download_container(
153
+ container: str, teamspace: Optional[str] = None, tag: str = "latest", cloud_account: Optional[str] = None
154
+ ) -> None:
147
155
  """Download a docker container from a teamspace.
148
156
 
149
157
  Example:
@@ -156,7 +164,7 @@ def download_container(container: str, teamspace: Optional[str] = None, tag: str
156
164
  resolved_teamspace = menu._resolve_teamspace(teamspace)
157
165
  with console.status("Downloading container..."):
158
166
  api = LitContainerApi()
159
- api.download_container(container, resolved_teamspace, tag)
167
+ api.download_container(container, resolved_teamspace, tag, cloud_account)
160
168
  console.print("Container downloaded successfully", style="green")
161
169
 
162
170
 
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import subprocess
3
+ from datetime import datetime
3
4
  from pathlib import Path
4
5
  from typing import Optional, Union
5
6
 
@@ -9,11 +10,9 @@ from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
9
10
  from rich.prompt import Confirm
10
11
 
11
12
  from lightning_sdk import Machine, Teamspace
12
- from lightning_sdk.api.deployment_api import DeploymentApi
13
13
  from lightning_sdk.api.lit_container_api import LitContainerApi
14
- from lightning_sdk.cli.exceptions import StudioCliError
15
14
  from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
16
- from lightning_sdk.serve import _LitServeDeployer
15
+ from lightning_sdk.serve import _LitServeDeployer, authenticate
17
16
 
18
17
  _MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
19
18
 
@@ -109,7 +108,7 @@ def api(
109
108
  script_path: str,
110
109
  easy: bool,
111
110
  cloud: bool,
112
- name: str,
111
+ name: Optional[str],
113
112
  non_interactive: bool,
114
113
  machine: str,
115
114
  interruptible: bool,
@@ -137,6 +136,7 @@ def api(
137
136
  user=user,
138
137
  cloud_account=cloud_account,
139
138
  port=port,
139
+ replicas=replicas,
140
140
  min_replica=min_replica,
141
141
  max_replica=max_replica,
142
142
  include_credentials=not no_credentials,
@@ -172,6 +172,10 @@ def api_impl(
172
172
 
173
173
  _LitServeDeployer.generate_client() if easy else None
174
174
 
175
+ if not repository:
176
+ timestr = datetime.now().strftime("%b-%d-%H_%M")
177
+ repository = f"litserve-{timestr}".lower()
178
+
175
179
  if cloud:
176
180
  repository = repository or "litserve-model"
177
181
  machine = Machine.from_str(machine)
@@ -223,6 +227,14 @@ def _handle_cloud(
223
227
  replicas: Optional[int] = 1,
224
228
  include_credentials: Optional[bool] = True,
225
229
  ) -> None:
230
+ deployment_name = os.path.basename(repository)
231
+ tag = tag if tag else "latest"
232
+
233
+ if non_interactive:
234
+ console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
235
+
236
+ # Authenticate with LitServe affiliate
237
+ authenticate()
226
238
  if teamspace is None:
227
239
  menu = _TeamspacesMenu()
228
240
  resolved_teamspace = menu._resolve_teamspace(teamspace)
@@ -230,11 +242,8 @@ def _handle_cloud(
230
242
  resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
231
243
 
232
244
  port = port or 8000
233
- ls_deployer = _LitServeDeployer()
245
+ ls_deployer = _LitServeDeployer(name=deployment_name, teamspace=resolved_teamspace)
234
246
  path = ls_deployer.dockerize_api(script_path, port=port, gpu=not machine.is_cpu(), tag=tag, print_success=False)
235
- console.clear()
236
- if non_interactive:
237
- console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
238
247
 
239
248
  console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
240
249
  correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
@@ -242,14 +251,9 @@ def _handle_cloud(
242
251
  console.print("Please fix the Dockerfile and try again.", style="red")
243
252
  return
244
253
 
245
- tag = tag if tag else "latest"
246
-
254
+ # list containers to create the project if it doesn't exist
247
255
  lit_cr = LitContainerApi()
248
- deployment_name = os.path.basename(repository)
249
-
250
- ls_deployer.authenticate()
251
- if DeploymentApi().get_deployment_by_name(deployment_name, resolved_teamspace.id):
252
- raise StudioCliError(f"Deployment {deployment_name} already exists. Please choose a different name.") from None
256
+ lit_cr.list_containers(resolved_teamspace.id, cloud_account=cloud_account)
253
257
 
254
258
  with Progress(
255
259
  SpinnerColumn(),
@@ -259,18 +263,21 @@ def _handle_cloud(
259
263
  transient=True,
260
264
  ) as progress:
261
265
  try:
266
+ # TODO: @aniketmaurya improve the console prints here
262
267
  ls_deployer.build_container(path, repository, tag, console, progress)
263
- push_status = ls_deployer.push_container(repository, tag, resolved_teamspace, lit_cr, progress)
268
+ push_status = ls_deployer.push_container(
269
+ repository, tag, resolved_teamspace, lit_cr, progress, cloud_account=cloud_account
270
+ )
264
271
  except Exception as e:
265
272
  console.print(f"❌ Deployment failed: {e}", style="red")
266
273
  return
267
274
  console.print(f"\n✅ Image pushed to {repository}:{tag}")
268
275
  console.print(f"🔗 You can access the container at: [i]{push_status.get('url')}[/i]")
269
- repository = push_status.get("repository")
276
+ image = push_status.get("repository")
270
277
 
271
- deployment_status = ls_deployer._run_on_cloud(
278
+ deployment_status = ls_deployer.run_on_cloud(
272
279
  deployment_name=deployment_name,
273
- image=repository,
280
+ image=image,
274
281
  teamspace=resolved_teamspace,
275
282
  metric=None,
276
283
  machine=machine,
@@ -11,11 +11,12 @@ from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
11
11
  from simple_term_menu import TerminalMenu
12
12
  from tqdm import tqdm
13
13
 
14
- from lightning_sdk.api.lit_container_api import LCRAuthFailedError, LitContainerApi
14
+ from lightning_sdk.api.lit_container_api import DockerNotRunningError, LCRAuthFailedError, LitContainerApi
15
15
  from lightning_sdk.api.utils import _get_cloud_url
16
16
  from lightning_sdk.cli.exceptions import StudioCliError
17
17
  from lightning_sdk.cli.studios_menu import _StudiosMenu
18
18
  from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
19
+ from lightning_sdk.constants import _LIGHTNING_DEBUG
19
20
  from lightning_sdk.models import upload_model as _upload_model
20
21
  from lightning_sdk.studio import Studio
21
22
  from lightning_sdk.utils.resolve import _get_authed_user, skip_studio_init
@@ -116,11 +117,28 @@ def file(path: str, studio: Optional[str] = None, remote_path: Optional[str] = N
116
117
  "If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
117
118
  ),
118
119
  )
119
- def upload_container(container: str, tag: str = "latest", teamspace: Optional[str] = None) -> None:
120
+ @click.option(
121
+ "--cloud-account",
122
+ "--cloud_account", # The UI will present the above variant, using this as a secondary to be consistent w/ models
123
+ default=None,
124
+ help="The name of the cloud account to store the Container in.",
125
+ )
126
+ @click.option(
127
+ "--platform",
128
+ "--platform",
129
+ default="linux/amd64",
130
+ help="This is the platform the container pulled and push to Lightning AI will run on.",
131
+ )
132
+ def upload_container(
133
+ container: str,
134
+ tag: str = "latest",
135
+ teamspace: Optional[str] = None,
136
+ cloud_account: Optional[str] = None,
137
+ platform: Optional[str] = "linux/amd64",
138
+ ) -> None:
120
139
  """Upload a container to Lightning AI's container registry."""
121
140
  menu = _TeamspacesMenu()
122
141
  teamspace = menu._resolve_teamspace(teamspace)
123
- api = LitContainerApi()
124
142
  console = Console()
125
143
  with Progress(
126
144
  SpinnerColumn(),
@@ -129,8 +147,13 @@ def upload_container(container: str, tag: str = "latest", teamspace: Optional[st
129
147
  console=console,
130
148
  transient=False,
131
149
  ) as progress:
132
- push_task = progress.add_task("Pushing Docker image", total=None)
133
150
  try:
151
+ if platform != "linux/amd64":
152
+ console.print(
153
+ "[yellow]Warning: The platform you selected (" + platform + ") may not deploy successfully[/yellow]"
154
+ )
155
+ api = LitContainerApi()
156
+ push_task = progress.add_task("Pushing Docker image", total=None)
134
157
  console.print("Authenticating with Lightning Container Registry...")
135
158
  try:
136
159
  api.authenticate()
@@ -139,15 +162,27 @@ def upload_container(container: str, tag: str = "latest", teamspace: Optional[st
139
162
  # let the push with retry take control of auth moving forward
140
163
  pass
141
164
 
142
- lines = api.upload_container(container, teamspace, tag)
165
+ lines = api.upload_container(container, teamspace, tag, cloud_account, platform)
143
166
  _print_docker_push(lines, console, progress, push_task)
167
+ except DockerNotRunningError as e:
168
+ e.print_help()
169
+ return
144
170
  except LCRAuthFailedError:
145
171
  console.print("Re-authenticating with Lightning Container Registry...")
146
172
  if not api.authenticate(reauth=True):
147
173
  raise StudioCliError("Failed to authenticate with Lightning Container Registry") from None
148
174
  console.print("Authenticated with Lightning Container Registry", style="green")
149
- lines = api.upload_container(container, teamspace, tag)
175
+ lines = api.upload_container(container, teamspace, tag, cloud_account, platform)
150
176
  _print_docker_push(lines, console, progress, push_task)
177
+ except Exception as e:
178
+ if _LIGHTNING_DEBUG:
179
+ print(e)
180
+ if e.__cause__:
181
+ print(e.__cause__)
182
+
183
+ progress.update(push_task, description=f"[bold red]Error: {e!s}[/]")
184
+ progress.stop()
185
+ return
151
186
  progress.update(push_task, description="[green]Container pushed![/green]")
152
187
 
153
188
 
@@ -66,6 +66,8 @@ from lightning_sdk.lightning_cloud.openapi.configuration import Configuration
66
66
  from lightning_sdk.lightning_cloud.openapi.models.affiliatelinks_id_body import AffiliatelinksIdBody
67
67
  from lightning_sdk.lightning_cloud.openapi.models.agentmanagedendpoints_id_body import AgentmanagedendpointsIdBody
68
68
  from lightning_sdk.lightning_cloud.openapi.models.agents_id_body import AgentsIdBody
69
+ from lightning_sdk.lightning_cloud.openapi.models.alerts_config_billing import AlertsConfigBilling
70
+ from lightning_sdk.lightning_cloud.openapi.models.alerts_config_studios import AlertsConfigStudios
69
71
  from lightning_sdk.lightning_cloud.openapi.models.app_id_works_body import AppIdWorksBody
70
72
  from lightning_sdk.lightning_cloud.openapi.models.appinstances_id_body import AppinstancesIdBody
71
73
  from lightning_sdk.lightning_cloud.openapi.models.approveautojoindomain_domain_body import ApproveautojoindomainDomainBody
@@ -196,6 +198,7 @@ from lightning_sdk.lightning_cloud.openapi.models.rpc_status import RpcStatus
196
198
  from lightning_sdk.lightning_cloud.openapi.models.schedules_id_body import SchedulesIdBody
197
199
  from lightning_sdk.lightning_cloud.openapi.models.secrets_id_body import SecretsIdBody
198
200
  from lightning_sdk.lightning_cloud.openapi.models.secrets_id_body1 import SecretsIdBody1
201
+ from lightning_sdk.lightning_cloud.openapi.models.server_id_alerts_body import ServerIdAlertsBody
199
202
  from lightning_sdk.lightning_cloud.openapi.models.servers_server_id_body import ServersServerIdBody
200
203
  from lightning_sdk.lightning_cloud.openapi.models.service_artifact_artifact_kind import ServiceArtifactArtifactKind
201
204
  from lightning_sdk.lightning_cloud.openapi.models.service_health_service_status import ServiceHealthServiceStatus
@@ -232,6 +235,8 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_agent_job import V1AgentJob
232
235
  from lightning_sdk.lightning_cloud.openapi.models.v1_agent_job_artifact import V1AgentJobArtifact
233
236
  from lightning_sdk.lightning_cloud.openapi.models.v1_agent_upload_multipart_url import V1AgentUploadMultipartUrl
234
237
  from lightning_sdk.lightning_cloud.openapi.models.v1_agent_upload_part_response import V1AgentUploadPartResponse
238
+ from lightning_sdk.lightning_cloud.openapi.models.v1_alert_method import V1AlertMethod
239
+ from lightning_sdk.lightning_cloud.openapi.models.v1_alerts_config import V1AlertsConfig
235
240
  from lightning_sdk.lightning_cloud.openapi.models.v1_api_pricing_spec import V1ApiPricingSpec
236
241
  from lightning_sdk.lightning_cloud.openapi.models.v1_app_type import V1AppType
237
242
  from lightning_sdk.lightning_cloud.openapi.models.v1_append_logger_metrics_response import V1AppendLoggerMetricsResponse
@@ -271,8 +276,10 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event
271
276
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event_type import V1CloudSpaceArtifactEventType
272
277
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version import V1CloudSpaceCodeVersion
273
278
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version_status import V1CloudSpaceCodeVersionStatus
279
+ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_cold_start_metrics import V1CloudSpaceColdStartMetrics
274
280
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_engagement_response import V1CloudSpaceEngagementResponse
275
281
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template import V1CloudSpaceEnvironmentTemplate
282
+ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template_config import V1CloudSpaceEnvironmentTemplateConfig
276
283
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_instance_collab_status import V1CloudSpaceInstanceCollabStatus
277
284
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_instance_config import V1CloudSpaceInstanceConfig
278
285
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_instance_startup_status import V1CloudSpaceInstanceStartupStatus
@@ -346,6 +353,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_create_organization_request
346
353
  from lightning_sdk.lightning_cloud.openapi.models.v1_create_pipeline_template_request import V1CreatePipelineTemplateRequest
347
354
  from lightning_sdk.lightning_cloud.openapi.models.v1_create_project_request import V1CreateProjectRequest
348
355
  from lightning_sdk.lightning_cloud.openapi.models.v1_create_ssh_public_key_request import V1CreateSSHPublicKeyRequest
356
+ from lightning_sdk.lightning_cloud.openapi.models.v1_create_server_alert_response import V1CreateServerAlertResponse
349
357
  from lightning_sdk.lightning_cloud.openapi.models.v1_create_shared_metrics_stream_request import V1CreateSharedMetricsStreamRequest
350
358
  from lightning_sdk.lightning_cloud.openapi.models.v1_create_shared_metrics_stream_response import V1CreateSharedMetricsStreamResponse
351
359
  from lightning_sdk.lightning_cloud.openapi.models.v1_create_snowflake_connection_response import V1CreateSnowflakeConnectionResponse
@@ -571,6 +579,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_list_agent_job_artifacts_re
571
579
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_agent_jobs_response import V1ListAgentJobsResponse
572
580
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_assistants_response import V1ListAssistantsResponse
573
581
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_apps_response import V1ListCloudSpaceAppsResponse
582
+ from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_cold_start_metrics_response import V1ListCloudSpaceColdStartMetricsResponse
574
583
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_environment_templates_response import V1ListCloudSpaceEnvironmentTemplatesResponse
575
584
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_instances_response import V1ListCloudSpaceInstancesResponse
576
585
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_publications_response import V1ListCloudSpacePublicationsResponse
@@ -765,11 +774,13 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_request import V1Re
765
774
  from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_response import V1RefreshResponse
766
775
  from lightning_sdk.lightning_cloud.openapi.models.v1_region_state import V1RegionState
767
776
  from lightning_sdk.lightning_cloud.openapi.models.v1_regional_load_balancer import V1RegionalLoadBalancer
777
+ from lightning_sdk.lightning_cloud.openapi.models.v1_report_cloud_space_instance_stop_at_response import V1ReportCloudSpaceInstanceStopAtResponse
768
778
  from lightning_sdk.lightning_cloud.openapi.models.v1_report_logs_activity_response import V1ReportLogsActivityResponse
769
779
  from lightning_sdk.lightning_cloud.openapi.models.v1_report_restart_timings_response import V1ReportRestartTimingsResponse
770
780
  from lightning_sdk.lightning_cloud.openapi.models.v1_request_cluster_access_request import V1RequestClusterAccessRequest
771
781
  from lightning_sdk.lightning_cloud.openapi.models.v1_request_cluster_access_response import V1RequestClusterAccessResponse
772
782
  from lightning_sdk.lightning_cloud.openapi.models.v1_request_verification_code_response import V1RequestVerificationCodeResponse
783
+ from lightning_sdk.lightning_cloud.openapi.models.v1_reservation_details import V1ReservationDetails
773
784
  from lightning_sdk.lightning_cloud.openapi.models.v1_resource_tag import V1ResourceTag
774
785
  from lightning_sdk.lightning_cloud.openapi.models.v1_resource_visibility import V1ResourceVisibility
775
786
  from lightning_sdk.lightning_cloud.openapi.models.v1_resources import V1Resources
@@ -797,6 +808,10 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_search_users_response impor
797
808
  from lightning_sdk.lightning_cloud.openapi.models.v1_secret import V1Secret
798
809
  from lightning_sdk.lightning_cloud.openapi.models.v1_secret_type import V1SecretType
799
810
  from lightning_sdk.lightning_cloud.openapi.models.v1_select import V1Select
811
+ from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert import V1ServerAlert
812
+ from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_phase import V1ServerAlertPhase
813
+ from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_severity import V1ServerAlertSeverity
814
+ from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_type import V1ServerAlertType
800
815
  from lightning_sdk.lightning_cloud.openapi.models.v1_server_check_in_response import V1ServerCheckInResponse
801
816
  from lightning_sdk.lightning_cloud.openapi.models.v1_service_artifact import V1ServiceArtifact
802
817
  from lightning_sdk.lightning_cloud.openapi.models.v1_service_execution import V1ServiceExecution
@@ -821,6 +836,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_storage_asset_type import V
821
836
  from lightning_sdk.lightning_cloud.openapi.models.v1_storage_system_metrics import V1StorageSystemMetrics
822
837
  from lightning_sdk.lightning_cloud.openapi.models.v1_studio_job import V1StudioJob
823
838
  from lightning_sdk.lightning_cloud.openapi.models.v1_studio_job_app import V1StudioJobApp
839
+ from lightning_sdk.lightning_cloud.openapi.models.v1_subnet_spec import V1SubnetSpec
824
840
  from lightning_sdk.lightning_cloud.openapi.models.v1_switch_cloud_space_instance_response import V1SwitchCloudSpaceInstanceResponse
825
841
  from lightning_sdk.lightning_cloud.openapi.models.v1_system_info import V1SystemInfo
826
842
  from lightning_sdk.lightning_cloud.openapi.models.v1_system_metrics import V1SystemMetrics
@@ -334,12 +334,12 @@ class CloudSpaceEnvironmentTemplateServiceApi(object):
334
334
  _request_timeout=params.get('_request_timeout'),
335
335
  collection_formats=collection_formats)
336
336
 
337
- def cloud_space_environment_template_service_list_get_cloud_space_environment_templates(self, **kwargs) -> 'V1ListCloudSpaceEnvironmentTemplatesResponse': # noqa: E501
338
- """cloud_space_environment_template_service_list_get_cloud_space_environment_templates # noqa: E501
337
+ def cloud_space_environment_template_service_list_cloud_space_environment_templates(self, **kwargs) -> 'V1ListCloudSpaceEnvironmentTemplatesResponse': # noqa: E501
338
+ """cloud_space_environment_template_service_list_cloud_space_environment_templates # noqa: E501
339
339
 
340
340
  This method makes a synchronous HTTP request by default. To make an
341
341
  asynchronous HTTP request, please pass async_req=True
342
- >>> thread = api.cloud_space_environment_template_service_list_get_cloud_space_environment_templates(async_req=True)
342
+ >>> thread = api.cloud_space_environment_template_service_list_cloud_space_environment_templates(async_req=True)
343
343
  >>> result = thread.get()
344
344
 
345
345
  :param async_req bool
@@ -352,17 +352,17 @@ class CloudSpaceEnvironmentTemplateServiceApi(object):
352
352
  """
353
353
  kwargs['_return_http_data_only'] = True
354
354
  if kwargs.get('async_req'):
355
- return self.cloud_space_environment_template_service_list_get_cloud_space_environment_templates_with_http_info(**kwargs) # noqa: E501
355
+ return self.cloud_space_environment_template_service_list_cloud_space_environment_templates_with_http_info(**kwargs) # noqa: E501
356
356
  else:
357
- (data) = self.cloud_space_environment_template_service_list_get_cloud_space_environment_templates_with_http_info(**kwargs) # noqa: E501
357
+ (data) = self.cloud_space_environment_template_service_list_cloud_space_environment_templates_with_http_info(**kwargs) # noqa: E501
358
358
  return data
359
359
 
360
- def cloud_space_environment_template_service_list_get_cloud_space_environment_templates_with_http_info(self, **kwargs) -> 'V1ListCloudSpaceEnvironmentTemplatesResponse': # noqa: E501
361
- """cloud_space_environment_template_service_list_get_cloud_space_environment_templates # noqa: E501
360
+ def cloud_space_environment_template_service_list_cloud_space_environment_templates_with_http_info(self, **kwargs) -> 'V1ListCloudSpaceEnvironmentTemplatesResponse': # noqa: E501
361
+ """cloud_space_environment_template_service_list_cloud_space_environment_templates # noqa: E501
362
362
 
363
363
  This method makes a synchronous HTTP request by default. To make an
364
364
  asynchronous HTTP request, please pass async_req=True
365
- >>> thread = api.cloud_space_environment_template_service_list_get_cloud_space_environment_templates_with_http_info(async_req=True)
365
+ >>> thread = api.cloud_space_environment_template_service_list_cloud_space_environment_templates_with_http_info(async_req=True)
366
366
  >>> result = thread.get()
367
367
 
368
368
  :param async_req bool
@@ -385,7 +385,7 @@ class CloudSpaceEnvironmentTemplateServiceApi(object):
385
385
  if key not in all_params:
386
386
  raise TypeError(
387
387
  "Got an unexpected keyword argument '%s'"
388
- " to method cloud_space_environment_template_service_list_get_cloud_space_environment_templates" % key
388
+ " to method cloud_space_environment_template_service_list_cloud_space_environment_templates" % key
389
389
  )
390
390
  params[key] = val
391
391
  del params['kwargs']