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 , TooManyRetriesError , StreamLogError ):
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,11 +456,6 @@ 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 ()
@@ -471,6 +471,8 @@ def _wait_for_deployment(
471471 ) as progress ,
472472 APIClient () as client ,
473473 ):
474+ build_complete = False
475+
474476 try :
475477 for log in client .stream_build_logs (deployment .id ):
476478 time_elapsed = time .monotonic () - started_at
@@ -479,18 +481,8 @@ def _wait_for_deployment(
479481 progress .log (Text .from_ansi (log .message .rstrip ()))
480482
481483 if log .type == "complete" :
484+ build_complete = True
482485 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-
494486 break
495487
496488 if log .type == "failed" :
@@ -519,6 +511,11 @@ def _wait_for_deployment(
519511
520512 raise typer .Exit (1 ) from None
521513
514+ if build_complete :
515+ toolkit .print_line ()
516+
517+ _verify_deployment (toolkit , client , app_id , deployment )
518+
522519
523520class SignupToWaitingList (BaseModel ):
524521 email : EmailStr
0 commit comments