44import subprocess
55import tempfile
66import time
7- from enum import Enum
87from itertools import cycle
98from pathlib import Path , PurePosixPath
109from textwrap import dedent
1817from rich .text import Text
1918from rich_toolkit import RichToolkit
2019from rich_toolkit .menu import Option
20+ from rich_toolkit .progress import Progress
2121
2222from fastapi_cloud_cli .commands .login import login
23- from fastapi_cloud_cli .utils .api import APIClient , StreamLogError , TooManyRetriesError
23+ from fastapi_cloud_cli .utils .api import (
24+ SUCCESSFUL_STATUSES ,
25+ APIClient ,
26+ DeploymentStatus ,
27+ StreamLogError ,
28+ TooManyRetriesError ,
29+ )
2430from fastapi_cloud_cli .utils .apps import AppConfig , get_app_config , write_app_config
2531from fastapi_cloud_cli .utils .auth import Identity
2632from fastapi_cloud_cli .utils .cli import get_rich_toolkit , handle_http_errors
@@ -174,42 +180,6 @@ def _create_app(team_id: str, app_name: str, directory: str | None) -> AppRespon
174180 return AppResponse .model_validate (response .json ())
175181
176182
177- class DeploymentStatus (str , Enum ):
178- waiting_upload = "waiting_upload"
179- ready_for_build = "ready_for_build"
180- building = "building"
181- extracting = "extracting"
182- extracting_failed = "extracting_failed"
183- building_image = "building_image"
184- building_image_failed = "building_image_failed"
185- deploying = "deploying"
186- deploying_failed = "deploying_failed"
187- verifying = "verifying"
188- verifying_failed = "verifying_failed"
189- verifying_skipped = "verifying_skipped"
190- success = "success"
191- failed = "failed"
192-
193- @classmethod
194- def to_human_readable (cls , status : "DeploymentStatus" ) -> str :
195- return {
196- cls .waiting_upload : "Waiting for upload" ,
197- cls .ready_for_build : "Ready for build" ,
198- cls .building : "Building" ,
199- cls .extracting : "Extracting" ,
200- cls .extracting_failed : "Extracting failed" ,
201- cls .building_image : "Building image" ,
202- cls .building_image_failed : "Build failed" ,
203- cls .deploying : "Deploying" ,
204- cls .deploying_failed : "Deploying failed" ,
205- cls .verifying : "Verifying" ,
206- cls .verifying_failed : "Verifying failed" ,
207- cls .verifying_skipped : "Verification skipped" ,
208- cls .success : "Success" ,
209- cls .failed : "Failed" ,
210- }[status ]
211-
212-
213183class CreateDeploymentResponse (BaseModel ):
214184 id : str
215185 app_id : str
@@ -440,6 +410,45 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
440410 return app_config
441411
442412
413+ def _verify_deployment (
414+ toolkit : RichToolkit ,
415+ client : APIClient ,
416+ app_id : str ,
417+ deployment : CreateDeploymentResponse ,
418+ ) -> None :
419+ with Progress (
420+ title = "Verifying deployment..." ,
421+ console = toolkit .console ,
422+ style = toolkit .style ,
423+ inline_logs = True ,
424+ ) as progress :
425+ progress .metadata ["done_emoji" ] = "✅"
426+ try :
427+ final_status = client .poll_deployment_status (app_id , deployment .id )
428+ except TimeoutError :
429+ progress .metadata ["done_emoji" ] = "⚠️"
430+ progress .log (
431+ f"Could not confirm deployment status. "
432+ f"Check the dashboard: [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
433+ )
434+ return
435+
436+ if final_status in SUCCESSFUL_STATUSES :
437+ progress .title = "Deployment verified!"
438+ progress .log (
439+ f"🐔 Ready the chicken! Your app is ready at [link={ deployment .url } ]{ deployment .url } [/link]"
440+ )
441+ else :
442+ progress .metadata ["done_emoji" ] = "❌"
443+ progress .title = "Deployment failed"
444+ human_status = DeploymentStatus .to_human_readable (final_status )
445+ progress .log (
446+ f"😔 Oh no! Deployment failed: { human_status } . "
447+ f"Check out the logs at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
448+ )
449+ raise typer .Exit (1 )
450+
451+
443452def _wait_for_deployment (
444453 toolkit : RichToolkit , app_id : str , deployment : CreateDeploymentResponse
445454) -> None :
@@ -451,73 +460,63 @@ def _wait_for_deployment(
451460 )
452461 toolkit .print_line ()
453462
454- toolkit .print (
455- f"You can also check the status at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]" ,
456- )
457- toolkit .print_line ()
458-
459463 time_elapsed = 0.0
460464
461465 started_at = time .monotonic ()
462466
463467 last_message_changed_at = time .monotonic ()
464468
465- with (
466- toolkit .progress (
469+ build_complete = False
470+
471+ with APIClient () as client :
472+ with toolkit .progress (
467473 next (messages ),
468474 inline_logs = True ,
469475 lines_to_show = 20 ,
470476 done_emoji = "🚀" ,
471- ) as progress ,
472- APIClient () as client ,
473- ):
474- try :
475- for log in client .stream_build_logs (deployment .id ):
476- time_elapsed = time .monotonic () - started_at
477+ ) as progress :
478+ try :
479+ for log in client .stream_build_logs (deployment .id ):
480+ time_elapsed = time .monotonic () - started_at
477481
478- if log .type == "message" :
479- progress .log (Text .from_ansi (log .message .rstrip ()))
482+ if log .type == "message" :
483+ progress .log (Text .from_ansi (log .message .rstrip ()))
480484
481- if log .type == "complete" :
482- progress .title = "Build complete!"
483- progress .log ("" )
484- progress .log (
485- f"You can also check the app logs at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
486- )
485+ if log .type == "complete" :
486+ build_complete = True
487+ progress .title = "Build complete!"
488+ break
487489
488- progress .log ("" )
490+ if log .type == "failed" :
491+ progress .log ("" )
492+ progress .log (
493+ f"😔 Oh no! Something went wrong. Check out the logs at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
494+ )
495+ raise typer .Exit (1 )
489496
490- progress .log (
491- f"🐔 Ready the chicken! Your app is ready at [link={ deployment .url } ]{ deployment .url } [/link]"
492- )
497+ if time_elapsed > 30 :
498+ messages = cycle (LONG_WAIT_MESSAGES )
493499
494- break
500+ if (time .monotonic () - last_message_changed_at ) > 2 :
501+ progress .title = next (messages )
495502
496- if log .type == "failed" :
497- progress .log ("" )
498- progress .log (
499- f"😔 Oh no! Something went wrong. Check out the logs at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
500- )
501- raise typer .Exit (1 )
503+ last_message_changed_at = time .monotonic ()
502504
503- if time_elapsed > 30 :
504- messages = cycle (LONG_WAIT_MESSAGES )
505-
506- if (time .monotonic () - last_message_changed_at ) > 2 :
507- progress .title = next (messages )
505+ except (StreamLogError , TooManyRetriesError , TimeoutError ) as e :
506+ progress .set_error (
507+ dedent (f"""
508+ [error]Build log streaming failed: { e } [/]
508509
509- last_message_changed_at = time .monotonic ()
510+ Unable to stream build logs. Check the dashboard for status: [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]
511+ """ ).strip ()
512+ )
510513
511- except (StreamLogError , TooManyRetriesError , TimeoutError ) as e :
512- progress .set_error (
513- dedent (f"""
514- [error]Build log streaming failed: { e } [/]
514+ raise typer .Exit (1 ) from None
515515
516- Unable to stream build logs. Check the dashboard for status: [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]
517- """ ).strip ()
518- )
516+ if build_complete :
517+ toolkit .print_line ()
519518
520- raise typer . Exit ( 1 ) from None
519+ _verify_deployment ( toolkit , client , app_id , deployment )
521520
522521
523522class SignupToWaitingList (BaseModel ):
0 commit comments