44import subprocess
55import tempfile
66import time
7- from enum import Enum
87from itertools import cycle
98from pathlib import Path , PurePosixPath
109from textwrap import dedent
2019from rich_toolkit .menu import Option
2120
2221from fastapi_cloud_cli .commands .login import login
23- from fastapi_cloud_cli .utils .api import APIClient , StreamLogError , TooManyRetriesError
22+ from fastapi_cloud_cli .utils .api import (
23+ SUCCESSFUL_STATUSES ,
24+ APIClient ,
25+ DeploymentStatus ,
26+ StreamLogError ,
27+ TooManyRetriesError ,
28+ )
2429from fastapi_cloud_cli .utils .apps import AppConfig , get_app_config , write_app_config
2530from fastapi_cloud_cli .utils .auth import Identity
2631from fastapi_cloud_cli .utils .cli import get_rich_toolkit , handle_http_errors
@@ -174,42 +179,6 @@ def _create_app(team_id: str, app_name: str, directory: str | None) -> AppRespon
174179 return AppResponse .model_validate (response .json ())
175180
176181
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-
213182class CreateDeploymentResponse (BaseModel ):
214183 id : str
215184 app_id : str
@@ -440,6 +409,42 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
440409 return app_config
441410
442411
412+ def _verify_deployment (
413+ toolkit : RichToolkit ,
414+ client : APIClient ,
415+ app_id : str ,
416+ deployment : CreateDeploymentResponse ,
417+ ) -> None :
418+ with toolkit .progress (
419+ title = "Verifying deployment..." ,
420+ inline_logs = True ,
421+ done_emoji = "✅" ,
422+ ) as progress :
423+ try :
424+ final_status = client .poll_deployment_status (app_id , deployment .id )
425+ except TimeoutError :
426+ progress .metadata ["done_emoji" ] = "⚠️"
427+ progress .current_message = (
428+ f"Could not confirm deployment status. "
429+ f"Check the dashboard: [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
430+ )
431+ return
432+
433+ if final_status in SUCCESSFUL_STATUSES :
434+ progress .current_message = f"Ready the chicken! 🐔 Your app is ready at [link={ deployment .url } ]{ deployment .url } [/link]"
435+ else :
436+ progress .metadata ["done_emoji" ] = "❌"
437+ progress .current_message = "Deployment failed"
438+
439+ human_status = DeploymentStatus .to_human_readable (final_status )
440+
441+ progress .log (
442+ f"😔 Oh no! Deployment failed: { human_status } . "
443+ f"Check out the logs at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
444+ )
445+ raise typer .Exit (1 )
446+
447+
443448def _wait_for_deployment (
444449 toolkit : RichToolkit , app_id : str , deployment : CreateDeploymentResponse
445450) -> None :
@@ -451,73 +456,66 @@ def _wait_for_deployment(
451456 )
452457 toolkit .print_line ()
453458
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-
459459 time_elapsed = 0.0
460460
461461 started_at = time .monotonic ()
462462
463463 last_message_changed_at = time .monotonic ()
464464
465- with (
466- toolkit .progress (
465+ build_complete = False
466+
467+ with APIClient () as client :
468+ with toolkit .progress (
467469 next (messages ),
468470 inline_logs = True ,
469471 lines_to_show = 20 ,
470472 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-
478- if log .type == "message" :
479- progress .log (Text .from_ansi (log .message .rstrip ()))
480-
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- )
487-
488- progress .log ("" )
489-
490- progress .log (
491- f"🐔 Ready the chicken! Your app is ready at [link={ deployment .url } ]{ deployment .url } [/link]"
492- )
493-
494- break
495-
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 )
502-
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 )
473+ ) as progress :
474+ try :
475+ for log in client .stream_build_logs (deployment .id ):
476+ time_elapsed = time .monotonic () - started_at
477+
478+ if log .type == "message" :
479+ progress .log (Text .from_ansi (log .message .rstrip ()))
480+
481+ if log .type == "complete" :
482+ build_complete = True
483+ progress .title = "Build complete!"
484+ break
485+
486+ if log .type == "failed" :
487+ progress .log ("" )
488+ progress .log (
489+ f"😔 Oh no! Something went wrong. Check out the logs at [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]"
490+ )
491+ raise typer .Exit (1 )
492+
493+ if time_elapsed > 30 :
494+ messages = cycle (LONG_WAIT_MESSAGES )
495+
496+ if (time .monotonic () - last_message_changed_at ) > 2 :
497+ progress .title = next (messages )
498+
499+ last_message_changed_at = time .monotonic ()
500+
501+ except KeyboardInterrupt :
502+ progress .title = "Cancelled"
503+ raise
504+ except (StreamLogError , TooManyRetriesError , TimeoutError ) as e :
505+ progress .set_error (
506+ dedent (f"""
507+ [error]Build log streaming failed: { e } [/]
508508
509- last_message_changed_at = time .monotonic ()
509+ Unable to stream build logs. Check the dashboard for status: [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]
510+ """ ).strip ()
511+ )
510512
511- except (StreamLogError , TooManyRetriesError , TimeoutError ) as e :
512- progress .set_error (
513- dedent (f"""
514- [error]Build log streaming failed: { e } [/]
513+ raise typer .Exit (1 ) from None
515514
516- Unable to stream build logs. Check the dashboard for status: [link={ deployment .dashboard_url } ]{ deployment .dashboard_url } [/link]
517- """ ).strip ()
518- )
515+ if build_complete :
516+ toolkit .print_line ()
519517
520- raise typer . Exit ( 1 ) from None
518+ _verify_deployment ( toolkit , client , app_id , deployment )
521519
522520
523521class SignupToWaitingList (BaseModel ):
0 commit comments