Add back build log command to CLI (#2053)

This commit is contained in:
Martin Xu 2023-10-27 11:30:04 -07:00 committed by GitHub
parent a2e185cced
commit 23255d49d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 80 additions and 48 deletions

View File

@ -544,7 +544,7 @@ def deploy(
enabled_regions = pre_deploy_response.enabled_regions enabled_regions = pre_deploy_response.enabled_regions
except Exception as ex: except Exception as ex:
console.error(f"Unable to prepare deployment due to: {ex}") console.error(f"Unable to prepare deployment")
raise typer.Exit(1) from ex raise typer.Exit(1) from ex
# The app prefix should not change during the time of preparation # The app prefix should not change during the time of preparation
@ -572,7 +572,6 @@ def deploy(
key = key_candidate key = key_candidate
# Then CP needs to know the user's location, which requires user permission # Then CP needs to know the user's location, which requires user permission
console.debug(f"{enabled_regions=}")
while True: while True:
region_input = console.ask( region_input = console.ask(
"Region to deploy to. Enter to use default.", "Region to deploy to. Enter to use default.",
@ -669,10 +668,15 @@ def deploy(
console.print("Waiting for server to report progress ...") console.print("Waiting for server to report progress ...")
# Display the key events such as build, deploy, etc # Display the key events such as build, deploy, etc
asyncio.get_event_loop().run_until_complete( server_report_deploy_success = asyncio.get_event_loop().run_until_complete(
hosting.display_deploy_milestones(key, from_iso_timestamp=deploy_requested_at) hosting.display_deploy_milestones(key, from_iso_timestamp=deploy_requested_at)
) )
if not server_report_deploy_success:
console.error("Hosting server reports failure.")
console.error(
f"Check the server logs using `reflex deployments build-logs {key}`"
)
raise typer.Exit(1)
console.print("Waiting for the new deployment to come up") console.print("Waiting for the new deployment to come up")
backend_up = frontend_up = False backend_up = frontend_up = False
@ -741,7 +745,7 @@ def list_deployments(
try: try:
deployments = hosting.list_deployments() deployments = hosting.list_deployments()
except Exception as ex: except Exception as ex:
console.error(f"Unable to list deployments due to: {ex}") console.error(f"Unable to list deployments")
raise typer.Exit(1) from ex raise typer.Exit(1) from ex
if as_json: if as_json:
@ -768,7 +772,7 @@ def delete_deployment(
try: try:
hosting.delete_deployment(key) hosting.delete_deployment(key)
except Exception as ex: except Exception as ex:
console.error(f"Unable to delete deployment due to: {ex}") console.error(f"Unable to delete deployment")
raise typer.Exit(1) from ex raise typer.Exit(1) from ex
console.print(f"Successfully deleted [ {key} ].") console.print(f"Successfully deleted [ {key} ].")
@ -805,7 +809,7 @@ def get_deployment_status(
table = list(frontend_status.values()) table = list(frontend_status.values())
console.print(tabulate([table], headers=headers)) console.print(tabulate([table], headers=headers))
except Exception as ex: except Exception as ex:
console.error(f"Unable to get deployment status due to: {ex}") console.error(f"Unable to get deployment status")
raise typer.Exit(1) from ex raise typer.Exit(1) from ex
@ -822,7 +826,28 @@ def get_deployment_logs(
try: try:
asyncio.get_event_loop().run_until_complete(hosting.get_logs(key)) asyncio.get_event_loop().run_until_complete(hosting.get_logs(key))
except Exception as ex: except Exception as ex:
console.error(f"Unable to get deployment logs due to: {ex}") console.error(f"Unable to get deployment logs")
raise typer.Exit(1) from ex
@deployments_cli.command(name="build-logs")
def get_deployment_build_logs(
key: str = typer.Argument(..., help="The name of the deployment."),
loglevel: constants.LogLevel = typer.Option(
config.loglevel, help="The log level to use."
),
):
"""Get the logs for a deployment."""
console.set_log_level(loglevel)
console.print("Note: there is a few seconds delay for logs to be available.")
try:
# TODO: we need to find a way not to fetch logs
# that match the deployed app name but not previously of a different owner
# This should not happen often
asyncio.run(hosting.get_logs(key, log_type=hosting.LogType.BUILD_LOG))
except Exception as ex:
console.error(f"Unable to get deployment logs")
raise typer.Exit(1) from ex raise typer.Exit(1) from ex

View File

@ -44,7 +44,7 @@ DEPLOYMENT_LOGS_ENDPOINT = f'{config.cp_backend_url.replace("http", "ws")}/deplo
# Expected server response time to new deployment request. In seconds. # Expected server response time to new deployment request. In seconds.
DEPLOYMENT_PICKUP_DELAY = 30 DEPLOYMENT_PICKUP_DELAY = 30
# End of deployment workflow message. Used to determine if it is the last message from server. # End of deployment workflow message. Used to determine if it is the last message from server.
END_OF_DEPLOYMENT_MESSAGES = ["deploy success", "deploy failed"] END_OF_DEPLOYMENT_MESSAGES = ["deploy success"]
# How many iterations to try and print the deployment event messages from server during deployment. # How many iterations to try and print the deployment event messages from server during deployment.
DEPLOYMENT_EVENT_MESSAGES_RETRIES = 90 DEPLOYMENT_EVENT_MESSAGES_RETRIES = 90
# Timeout limit for http requests # Timeout limit for http requests
@ -93,7 +93,7 @@ def validate_token(token: str):
response.raise_for_status() response.raise_for_status()
except httpx.RequestError as re: except httpx.RequestError as re:
console.debug(f"Request to auth server failed due to {re}") console.debug(f"Request to auth server failed due to {re}")
raise Exception("request error") from re raise Exception(str(re)) from re
except httpx.HTTPError as ex: except httpx.HTTPError as ex:
console.debug(f"Unable to validate the token due to: {ex}") console.debug(f"Unable to validate the token due to: {ex}")
raise Exception("server error") from ex raise Exception("server error") from ex
@ -307,22 +307,22 @@ def prepare_deploy(
enabled_regions=response_json.get("enabled_regions"), enabled_regions=response_json.get("enabled_regions"),
) )
except httpx.RequestError as re: except httpx.RequestError as re:
console.debug(f"Unable to prepare launch due to {re}.") console.error(f"Unable to prepare launch due to {re}.")
raise Exception("request error") from re raise Exception(str(re)) from re
except httpx.HTTPError as he: except httpx.HTTPError as he:
console.debug(f"Unable to prepare deploy due to {he}.") console.error(f"Unable to prepare deploy due to {he}.")
raise Exception(f"{he}") from he raise Exception(f"{he}") from he
except json.JSONDecodeError as jde: except json.JSONDecodeError as jde:
console.debug(f"Server did not respond with valid json: {jde}") console.error(f"Server did not respond with valid json: {jde}")
raise Exception("internal errors") from jde raise Exception("internal errors") from jde
except (KeyError, ValidationError) as kve: except (KeyError, ValidationError) as kve:
console.debug(f"The server response format is unexpected {kve}") console.error(f"The server response format is unexpected {kve}")
raise Exception("internal errors") from kve raise Exception("internal errors") from kve
except ValueError as ve: except ValueError as ve:
# This is a recognized client error, currently indicates forbidden # This is a recognized client error, currently indicates forbidden
raise Exception(f"{ve}") from ve raise Exception(f"{ve}") from ve
except Exception as ex: except Exception as ex:
console.debug(f"Unexpected error: {ex}.") console.error(f"Unexpected error: {ex}.")
raise Exception("internal errors") from ex raise Exception("internal errors") from ex
@ -460,26 +460,26 @@ def deploy(
backend_url=response_json["backend_url"], backend_url=response_json["backend_url"],
) )
except OSError as oe: except OSError as oe:
console.debug(f"Client side error related to file operation: {oe}") console.error(f"Client side error related to file operation: {oe}")
raise raise
except httpx.RequestError as re: except httpx.RequestError as re:
console.debug(f"Unable to deploy due to request error: {re}") console.error(f"Unable to deploy due to request error: {re}")
raise Exception("request error") from re raise Exception("request error") from re
except httpx.HTTPError as he: except httpx.HTTPError as he:
console.debug(f"Unable to deploy due to {he}.") console.error(f"Unable to deploy due to {he}.")
raise Exception("internal errors") from he raise Exception(str) from he
except json.JSONDecodeError as jde: except json.JSONDecodeError as jde:
console.debug(f"Server did not respond with valid json: {jde}") console.error(f"Server did not respond with valid json: {jde}")
raise Exception("internal errors") from jde raise Exception("internal errors") from jde
except (KeyError, ValidationError) as kve: except (KeyError, ValidationError) as kve:
console.debug(f"Post params or server response format unexpected: {kve}") console.error(f"Post params or server response format unexpected: {kve}")
raise Exception("internal errors") from kve raise Exception("internal errors") from kve
except AssertionError as ve: except AssertionError as ve:
console.debug(f"Unable to deploy due to request error: {ve}") console.error(f"Unable to deploy due to request error: {ve}")
# re-raise the error back to the user as client side error # re-raise the error back to the user as client side error
raise raise
except Exception as ex: except Exception as ex:
console.debug(f"Unable to deploy due to internal errors: {ex}.") console.error(f"Unable to deploy due to internal errors: {ex}.")
raise Exception("internal errors") from ex raise Exception("internal errors") from ex
@ -552,13 +552,13 @@ def list_deployments(
for deployment in response.json() for deployment in response.json()
] ]
except httpx.RequestError as re: except httpx.RequestError as re:
console.debug(f"Unable to list deployments due to request error: {re}") console.error(f"Unable to list deployments due to request error: {re}")
raise Exception("request timeout") from re raise Exception("request timeout") from re
except httpx.HTTPError as he: except httpx.HTTPError as he:
console.debug(f"Unable to list deployments due to {he}.") console.error(f"Unable to list deployments due to {he}.")
raise Exception("internal errors") from he raise Exception("internal errors") from he
except (ValidationError, KeyError, json.JSONDecodeError) as vkje: except (ValidationError, KeyError, json.JSONDecodeError) as vkje:
console.debug(f"Server response format unexpected: {vkje}") console.error(f"Server response format unexpected: {vkje}")
raise Exception("internal errors") from vkje raise Exception("internal errors") from vkje
except Exception as ex: except Exception as ex:
console.error(f"Unexpected error: {ex}.") console.error(f"Unexpected error: {ex}.")
@ -584,15 +584,15 @@ def fetch_token(request_id: str) -> tuple[str, str]:
access_token = (resp_json := resp.json()).get("access_token", "") access_token = (resp_json := resp.json()).get("access_token", "")
invitation_code = resp_json.get("code", "") invitation_code = resp_json.get("code", "")
except httpx.RequestError as re: except httpx.RequestError as re:
console.debug(f"Unable to fetch token due to request error: {re}") console.error(f"Unable to fetch token due to request error: {re}")
except httpx.HTTPError as he: except httpx.HTTPError as he:
console.debug(f"Unable to fetch token due to {he}") console.error(f"Unable to fetch token due to {he}")
except json.JSONDecodeError as jde: except json.JSONDecodeError as jde:
console.debug(f"Server did not respond with valid json: {jde}") console.error(f"Server did not respond with valid json: {jde}")
except KeyError as ke: except KeyError as ke:
console.debug(f"Server response format unexpected: {ke}") console.error(f"Server response format unexpected: {ke}")
except Exception: except Exception:
console.debug("Unexpected errors: {ex}") console.error("Unexpected errors: {ex}")
return access_token, invitation_code return access_token, invitation_code
@ -608,7 +608,7 @@ def poll_backend(backend_url: str) -> bool:
""" """
try: try:
console.debug(f"Polling backend at {backend_url}") console.debug(f"Polling backend at {backend_url}")
resp = httpx.get(f"{backend_url}/ping", timeout=HTTP_REQUEST_TIMEOUT) resp = httpx.get(f"{backend_url}/ping", timeout=1)
resp.raise_for_status() resp.raise_for_status()
return True return True
except httpx.HTTPError: except httpx.HTTPError:
@ -626,7 +626,7 @@ def poll_frontend(frontend_url: str) -> bool:
""" """
try: try:
console.debug(f"Polling frontend at {frontend_url}") console.debug(f"Polling frontend at {frontend_url}")
resp = httpx.get(f"{frontend_url}", timeout=HTTP_REQUEST_TIMEOUT) resp = httpx.get(f"{frontend_url}", timeout=1)
resp.raise_for_status() resp.raise_for_status()
return True return True
except httpx.HTTPError: except httpx.HTTPError:
@ -664,13 +664,13 @@ def delete_deployment(key: str):
response.raise_for_status() response.raise_for_status()
except httpx.TimeoutException as te: except httpx.TimeoutException as te:
console.debug("Unable to delete deployment due to request timeout.") console.error("Unable to delete deployment due to request timeout.")
raise Exception("request timeout") from te raise Exception("request timeout") from te
except httpx.HTTPError as he: except httpx.HTTPError as he:
console.debug(f"Unable to delete deployment due to {he}.") console.error(f"Unable to delete deployment due to {he}.")
raise Exception("internal errors") from he raise Exception("internal errors") from he
except Exception as ex: except Exception as ex:
console.debug(f"Unexpected errors {ex}.") console.error(f"Unexpected errors {ex}.")
raise Exception("internal errors") from ex raise Exception("internal errors") from ex
@ -755,7 +755,7 @@ def get_deployment_status(key: str) -> DeploymentStatusResponse:
), ),
) )
except Exception as ex: except Exception as ex:
console.debug(f"Unable to get deployment status due to {ex}.") console.error(f"Unable to get deployment status due to {ex}.")
raise Exception("internal errors") from ex raise Exception("internal errors") from ex
@ -772,7 +772,7 @@ def convert_to_local_time(iso_timestamp: str) -> str:
local_dt = datetime.fromisoformat(iso_timestamp).astimezone() local_dt = datetime.fromisoformat(iso_timestamp).astimezone()
return local_dt.strftime("%Y-%m-%d %H:%M:%S.%f %Z") return local_dt.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
except Exception as ex: except Exception as ex:
console.debug(f"Unable to convert iso timestamp {iso_timestamp} due to {ex}.") console.error(f"Unable to convert iso timestamp {iso_timestamp} due to {ex}.")
return iso_timestamp return iso_timestamp
@ -1041,7 +1041,7 @@ def log_out_on_browser():
) )
async def display_deploy_milestones(key: str, from_iso_timestamp: datetime): async def display_deploy_milestones(key: str, from_iso_timestamp: datetime) -> bool:
"""Display the deploy milestone messages reported back from the hosting server. """Display the deploy milestone messages reported back from the hosting server.
Args: Args:
@ -1051,6 +1051,9 @@ async def display_deploy_milestones(key: str, from_iso_timestamp: datetime):
Raises: Raises:
ValueError: If a non-empty key is not provided. ValueError: If a non-empty key is not provided.
Exception: If the user is not authenticated. Exception: If the user is not authenticated.
Returns:
False if server reports back failure, True otherwise.
""" """
if not key: if not key:
raise ValueError("Non-empty key is required for querying deploy status.") raise ValueError("Non-empty key is required for querying deploy status.")
@ -1076,18 +1079,22 @@ async def display_deploy_milestones(key: str, from_iso_timestamp: datetime):
] ]
) )
) )
if any( server_message = row_json["message"].lower()
msg in row_json["message"].lower() if "fail" in server_message:
for msg in END_OF_DEPLOYMENT_MESSAGES console.debug(
): "Received failure message, stop event message streaming"
)
return False
if any(msg in server_message for msg in END_OF_DEPLOYMENT_MESSAGES):
console.debug( console.debug(
"Received end of deployment message, stop event message streaming" "Received end of deployment message, stop event message streaming"
) )
return return True
else: else:
console.debug("Server responded, no new events yet, this is normal") console.debug("Server responded, no new events yet, this is normal")
except Exception as ex: except Exception as ex:
console.debug(f"Unable to get more deployment events due to {ex}.") console.debug(f"Unable to get more deployment events due to {ex}.")
return False
def wait_for_server_to_pick_up_request(): def wait_for_server_to_pick_up_request():
@ -1141,16 +1148,16 @@ def get_regions() -> list[dict]:
response.raise_for_status() response.raise_for_status()
response_json = response.json() response_json = response.json()
if response_json is None or not isinstance(response_json, list): if response_json is None or not isinstance(response_json, list):
console.debug("Expect server to return a list ") console.error("Expect server to return a list ")
return [] return []
if ( if (
response_json response_json
and response_json[0] is not None and response_json[0] is not None
and not isinstance(response_json[0], dict) and not isinstance(response_json[0], dict)
): ):
console.debug("Expect return values are dict's") console.error("Expect return values are dict's")
return [] return []
return response_json return response_json
except Exception as ex: except Exception as ex:
console.debug(f"Unable to get regions due to {ex}.") console.error(f"Unable to get regions due to {ex}.")
return [] return []