|
1 | 1 | import contextlib |
2 | | -import json |
3 | 2 | import logging |
4 | 3 | import subprocess |
5 | 4 | import tarfile |
|
8 | 7 | from enum import Enum |
9 | 8 | from itertools import cycle |
10 | 9 | from pathlib import Path |
11 | | -from typing import Any, Dict, Generator, List, Optional, Union |
| 10 | +from typing import Any, Dict, List, Optional, Union |
12 | 11 |
|
13 | 12 | import rignore |
14 | 13 | import typer |
|
20 | 19 | from typing_extensions import Annotated |
21 | 20 |
|
22 | 21 | from fastapi_cloud_cli.commands.login import login |
23 | | -from fastapi_cloud_cli.utils.api import APIClient |
| 22 | +from fastapi_cloud_cli.utils.api import APIClient, BuildLogError, BuildLogType |
24 | 23 | from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config |
25 | 24 | from fastapi_cloud_cli.utils.auth import is_logged_in |
26 | 25 | from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors |
@@ -225,21 +224,11 @@ def _get_apps(team_id: str) -> List[AppResponse]: |
225 | 224 | return [AppResponse.model_validate(app) for app in data] |
226 | 225 |
|
227 | 226 |
|
228 | | -def _stream_build_logs(deployment_id: str) -> Generator[str, None, None]: |
229 | | - with APIClient() as client: |
230 | | - with client.stream( |
231 | | - "GET", f"/deployments/{deployment_id}/build-logs", timeout=60 |
232 | | - ) as response: |
233 | | - response.raise_for_status() |
234 | | - |
235 | | - yield from response.iter_lines() |
236 | | - |
237 | | - |
238 | 227 | WAITING_MESSAGES = [ |
239 | 228 | "🚀 Preparing for liftoff! Almost there...", |
240 | 229 | "👹 Sneaking past the dependency gremlins... Don't wake them up!", |
241 | 230 | "🤏 Squishing code into a tiny digital sandwich. Nom nom nom.", |
242 | | - "📉 Server space running low. Time to delete those cat videos?", |
| 231 | + "🐱 Removing cat videos from our servers to free up space.", |
243 | 232 | "🐢 Uploading at blazing speeds of 1 byte per hour. Patience, young padawan.", |
244 | 233 | "🔌 Connecting to server... Please stand by while we argue with the firewall.", |
245 | 234 | "💥 Oops! We've angered the Python God. Sacrificing a rubber duck to appease it.", |
@@ -350,43 +339,50 @@ def _wait_for_deployment( |
350 | 339 | with toolkit.progress( |
351 | 340 | next(messages), inline_logs=True, lines_to_show=20 |
352 | 341 | ) as progress: |
353 | | - with handle_http_errors(progress=progress): |
354 | | - for line in _stream_build_logs(deployment.id): |
355 | | - time_elapsed = time.monotonic() - started_at |
| 342 | + with APIClient() as client: |
| 343 | + try: |
| 344 | + for log in client.stream_build_logs(deployment.id): |
| 345 | + time_elapsed = time.monotonic() - started_at |
356 | 346 |
|
357 | | - data = json.loads(line) |
| 347 | + if log.type == BuildLogType.message and log.message: |
| 348 | + progress.log(Text.from_ansi(log.message.rstrip())) |
358 | 349 |
|
359 | | - if "message" in data: |
360 | | - progress.log(Text.from_ansi(data["message"].rstrip())) |
| 350 | + if log.type == BuildLogType.complete: |
| 351 | + progress.log("") |
| 352 | + progress.log( |
| 353 | + f"🐔 Ready the chicken! Your app is ready at [link={deployment.url}]{deployment.url}[/link]" |
| 354 | + ) |
361 | 355 |
|
362 | | - if data.get("type") == "complete": |
363 | | - progress.log("") |
364 | | - progress.log( |
365 | | - f"🐔 Ready the chicken! Your app is ready at [link={deployment.url}]{deployment.url}[/link]" |
366 | | - ) |
| 356 | + progress.log("") |
367 | 357 |
|
368 | | - progress.log("") |
| 358 | + progress.log( |
| 359 | + f"You can also check the app logs at [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]" |
| 360 | + ) |
369 | 361 |
|
370 | | - progress.log( |
371 | | - f"You can also check the app logs at [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]" |
372 | | - ) |
| 362 | + break |
373 | 363 |
|
374 | | - break |
| 364 | + if log.type == BuildLogType.failed: |
| 365 | + progress.log("") |
| 366 | + progress.log( |
| 367 | + f"😔 Oh no! Something went wrong. Check out the logs at [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]" |
| 368 | + ) |
| 369 | + raise typer.Exit(1) |
375 | 370 |
|
376 | | - if data.get("type") == "failed": |
377 | | - progress.log("") |
378 | | - progress.log( |
379 | | - f"😔 Oh no! Something went wrong. Check out the logs at [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]" |
380 | | - ) |
381 | | - raise typer.Exit(1) |
| 371 | + if time_elapsed > 30: |
| 372 | + messages = cycle(LONG_WAIT_MESSAGES) |
382 | 373 |
|
383 | | - if time_elapsed > 30: |
384 | | - messages = cycle(LONG_WAIT_MESSAGES) # pragma: no cover |
| 374 | + if (time.monotonic() - last_message_changed_at) > 2: |
| 375 | + progress.title = next(messages) |
385 | 376 |
|
386 | | - if (time.monotonic() - last_message_changed_at) > 2: |
387 | | - progress.title = next(messages) # pragma: no cover |
| 377 | + last_message_changed_at = time.monotonic() |
388 | 378 |
|
389 | | - last_message_changed_at = time.monotonic() # pragma: no cover |
| 379 | + except BuildLogError as e: |
| 380 | + logger.error("Build log streaming failed: %s", e) |
| 381 | + toolkit.print_line() |
| 382 | + toolkit.print( |
| 383 | + f"⚠️ Unable to stream build logs. Check the dashboard for status: [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]" |
| 384 | + ) |
| 385 | + raise typer.Exit(1) from e |
390 | 386 |
|
391 | 387 |
|
392 | 388 | class SignupToWaitingList(BaseModel): |
|
0 commit comments