lightning-sdk 0.2.6__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 (42) 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 +16 -29
  9. lightning_sdk/cli/upload.py +41 -6
  10. lightning_sdk/lightning_cloud/openapi/__init__.py +8 -0
  11. lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +202 -0
  12. lightning_sdk/lightning_cloud/openapi/models/__init__.py +8 -0
  13. lightning_sdk/lightning_cloud/openapi/models/alerts_config_billing.py +175 -0
  14. lightning_sdk/lightning_cloud/openapi/models/alerts_config_studios.py +149 -0
  15. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +53 -1
  16. lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +27 -1
  17. lightning_sdk/lightning_cloud/openapi/models/server_id_alerts_body.py +27 -1
  18. lightning_sdk/lightning_cloud/openapi/models/v1_alert_method.py +102 -0
  19. lightning_sdk/lightning_cloud/openapi/models/v1_alerts_config.py +149 -0
  20. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_cold_start_metrics.py +617 -0
  21. lightning_sdk/lightning_cloud/openapi/models/v1_conversation_response_chunk.py +29 -3
  22. lightning_sdk/lightning_cloud/openapi/models/v1_create_project_request.py +27 -1
  23. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +29 -3
  24. lightning_sdk/lightning_cloud/openapi/models/v1_list_cloud_space_cold_start_metrics_response.py +123 -0
  25. lightning_sdk/lightning_cloud/openapi/models/v1_message.py +29 -3
  26. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +53 -1
  27. lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +27 -1
  28. lightning_sdk/lightning_cloud/openapi/models/v1_report_cloud_space_instance_stop_at_response.py +97 -0
  29. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert.py +27 -1
  30. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_phase.py +104 -0
  31. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +53 -1
  32. lightning_sdk/lightning_cloud/rest_client.py +42 -44
  33. lightning_sdk/lit_container.py +19 -4
  34. lightning_sdk/models.py +1 -1
  35. lightning_sdk/serve.py +86 -17
  36. lightning_sdk/teamspace.py +32 -18
  37. {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.7.dist-info}/METADATA +1 -1
  38. {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.7.dist-info}/RECORD +42 -34
  39. {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.7.dist-info}/LICENSE +0 -0
  40. {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.7.dist-info}/WHEEL +0 -0
  41. {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.7.dist-info}/entry_points.txt +0 -0
  42. {lightning_sdk-0.2.6.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.6"
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
 
@@ -8,13 +8,11 @@ import click
8
8
  from rich.console import Console
9
9
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
10
10
  from rich.prompt import Confirm
11
- from rich.syntax import Syntax
12
11
 
13
12
  from lightning_sdk import Machine, Teamspace
14
- from lightning_sdk.api.deployment_api import DeploymentApi
15
13
  from lightning_sdk.api.lit_container_api import LitContainerApi
16
14
  from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
17
- from lightning_sdk.serve import _LitServeDeployer
15
+ from lightning_sdk.serve import _LitServeDeployer, authenticate
18
16
 
19
17
  _MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
20
18
 
@@ -231,34 +229,21 @@ def _handle_cloud(
231
229
  ) -> None:
232
230
  deployment_name = os.path.basename(repository)
233
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()
234
238
  if teamspace is None:
235
239
  menu = _TeamspacesMenu()
236
240
  resolved_teamspace = menu._resolve_teamspace(teamspace)
237
241
  else:
238
242
  resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
239
243
 
240
- if DeploymentApi().get_deployment_by_name(deployment_name, resolved_teamspace.id):
241
- syntax = Syntax(
242
- "from lightning_sdk import Deployment\n\n"
243
- f"deployment = Deployment('{deployment_name}', teamspace={resolved_teamspace.name})\n"
244
- "deployment.update(...)",
245
- "python",
246
- line_numbers=True,
247
- )
248
-
249
- console.print(
250
- f"\nDeployment with name [b]{deployment_name}[/b] already running. "
251
- "To update the deployment, use the Deployment API:\n",
252
- syntax,
253
- )
254
- return
255
-
256
244
  port = port or 8000
257
- ls_deployer = _LitServeDeployer()
245
+ ls_deployer = _LitServeDeployer(name=deployment_name, teamspace=resolved_teamspace)
258
246
  path = ls_deployer.dockerize_api(script_path, port=port, gpu=not machine.is_cpu(), tag=tag, print_success=False)
259
- console.clear()
260
- if non_interactive:
261
- console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
262
247
 
263
248
  console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
264
249
  correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
@@ -266,10 +251,9 @@ def _handle_cloud(
266
251
  console.print("Please fix the Dockerfile and try again.", style="red")
267
252
  return
268
253
 
269
- ls_deployer.authenticate()
270
254
  # list containers to create the project if it doesn't exist
271
255
  lit_cr = LitContainerApi()
272
- lit_cr.list_containers(resolved_teamspace.id)
256
+ lit_cr.list_containers(resolved_teamspace.id, cloud_account=cloud_account)
273
257
 
274
258
  with Progress(
275
259
  SpinnerColumn(),
@@ -279,18 +263,21 @@ def _handle_cloud(
279
263
  transient=True,
280
264
  ) as progress:
281
265
  try:
266
+ # TODO: @aniketmaurya improve the console prints here
282
267
  ls_deployer.build_container(path, repository, tag, console, progress)
283
- 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
+ )
284
271
  except Exception as e:
285
272
  console.print(f"❌ Deployment failed: {e}", style="red")
286
273
  return
287
274
  console.print(f"\n✅ Image pushed to {repository}:{tag}")
288
275
  console.print(f"🔗 You can access the container at: [i]{push_status.get('url')}[/i]")
289
- repository = push_status.get("repository")
276
+ image = push_status.get("repository")
290
277
 
291
- deployment_status = ls_deployer._run_on_cloud(
278
+ deployment_status = ls_deployer.run_on_cloud(
292
279
  deployment_name=deployment_name,
293
- image=repository,
280
+ image=image,
294
281
  teamspace=resolved_teamspace,
295
282
  metric=None,
296
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
@@ -233,6 +235,8 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_agent_job import V1AgentJob
233
235
  from lightning_sdk.lightning_cloud.openapi.models.v1_agent_job_artifact import V1AgentJobArtifact
234
236
  from lightning_sdk.lightning_cloud.openapi.models.v1_agent_upload_multipart_url import V1AgentUploadMultipartUrl
235
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
236
240
  from lightning_sdk.lightning_cloud.openapi.models.v1_api_pricing_spec import V1ApiPricingSpec
237
241
  from lightning_sdk.lightning_cloud.openapi.models.v1_app_type import V1AppType
238
242
  from lightning_sdk.lightning_cloud.openapi.models.v1_append_logger_metrics_response import V1AppendLoggerMetricsResponse
@@ -272,6 +276,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event
272
276
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event_type import V1CloudSpaceArtifactEventType
273
277
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version import V1CloudSpaceCodeVersion
274
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
275
280
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_engagement_response import V1CloudSpaceEngagementResponse
276
281
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template import V1CloudSpaceEnvironmentTemplate
277
282
  from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template_config import V1CloudSpaceEnvironmentTemplateConfig
@@ -574,6 +579,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_list_agent_job_artifacts_re
574
579
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_agent_jobs_response import V1ListAgentJobsResponse
575
580
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_assistants_response import V1ListAssistantsResponse
576
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
577
583
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_environment_templates_response import V1ListCloudSpaceEnvironmentTemplatesResponse
578
584
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_instances_response import V1ListCloudSpaceInstancesResponse
579
585
  from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_publications_response import V1ListCloudSpacePublicationsResponse
@@ -768,6 +774,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_request import V1Re
768
774
  from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_response import V1RefreshResponse
769
775
  from lightning_sdk.lightning_cloud.openapi.models.v1_region_state import V1RegionState
770
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
771
778
  from lightning_sdk.lightning_cloud.openapi.models.v1_report_logs_activity_response import V1ReportLogsActivityResponse
772
779
  from lightning_sdk.lightning_cloud.openapi.models.v1_report_restart_timings_response import V1ReportRestartTimingsResponse
773
780
  from lightning_sdk.lightning_cloud.openapi.models.v1_request_cluster_access_request import V1RequestClusterAccessRequest
@@ -802,6 +809,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_secret import V1Secret
802
809
  from lightning_sdk.lightning_cloud.openapi.models.v1_secret_type import V1SecretType
803
810
  from lightning_sdk.lightning_cloud.openapi.models.v1_select import V1Select
804
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
805
813
  from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_severity import V1ServerAlertSeverity
806
814
  from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_type import V1ServerAlertType
807
815
  from lightning_sdk.lightning_cloud.openapi.models.v1_server_check_in_response import V1ServerCheckInResponse