Browse Source

Add back build log command to CLI (#2053)

Martin Xu 1 year ago
parent
commit
23255d49d4
2 changed files with 80 additions and 48 deletions
  1. 33 8
      reflex/reflex.py
  2. 47 40
      reflex/utils/hosting.py

+ 33 - 8
reflex/reflex.py

@@ -544,7 +544,7 @@ def deploy(
             enabled_regions = pre_deploy_response.enabled_regions
 
     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
 
     # The app prefix should not change during the time of preparation
@@ -572,7 +572,6 @@ def deploy(
         key = key_candidate
 
         # Then CP needs to know the user's location, which requires user permission
-        console.debug(f"{enabled_regions=}")
         while True:
             region_input = console.ask(
                 "Region to deploy to. Enter to use default.",
@@ -669,10 +668,15 @@ def deploy(
 
     console.print("Waiting for server to report progress ...")
     # 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)
     )
-
+    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")
     backend_up = frontend_up = False
 
@@ -741,7 +745,7 @@ def list_deployments(
     try:
         deployments = hosting.list_deployments()
     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
 
     if as_json:
@@ -768,7 +772,7 @@ def delete_deployment(
     try:
         hosting.delete_deployment(key)
     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
     console.print(f"Successfully deleted [ {key} ].")
 
@@ -805,7 +809,7 @@ def get_deployment_status(
         table = list(frontend_status.values())
         console.print(tabulate([table], headers=headers))
     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
 
 
@@ -822,7 +826,28 @@ def get_deployment_logs(
     try:
         asyncio.get_event_loop().run_until_complete(hosting.get_logs(key))
     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
 
 

+ 47 - 40
reflex/utils/hosting.py

@@ -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.
 DEPLOYMENT_PICKUP_DELAY = 30
 # 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.
 DEPLOYMENT_EVENT_MESSAGES_RETRIES = 90
 # Timeout limit for http requests
@@ -93,7 +93,7 @@ def validate_token(token: str):
         response.raise_for_status()
     except httpx.RequestError as 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:
         console.debug(f"Unable to validate the token due to: {ex}")
         raise Exception("server error") from ex
@@ -307,22 +307,22 @@ def prepare_deploy(
             enabled_regions=response_json.get("enabled_regions"),
         )
     except httpx.RequestError as re:
-        console.debug(f"Unable to prepare launch due to {re}.")
-        raise Exception("request error") from re
+        console.error(f"Unable to prepare launch due to {re}.")
+        raise Exception(str(re)) from re
     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
     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
     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
     except ValueError as ve:
         # This is a recognized client error, currently indicates forbidden
         raise Exception(f"{ve}") from ve
     except Exception as ex:
-        console.debug(f"Unexpected error: {ex}.")
+        console.error(f"Unexpected error: {ex}.")
         raise Exception("internal errors") from ex
 
 
@@ -460,26 +460,26 @@ def deploy(
             backend_url=response_json["backend_url"],
         )
     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
     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
     except httpx.HTTPError as he:
-        console.debug(f"Unable to deploy due to {he}.")
-        raise Exception("internal errors") from he
+        console.error(f"Unable to deploy due to {he}.")
+        raise Exception(str) from he
     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
     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
     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
         raise
     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
 
 
@@ -552,13 +552,13 @@ def list_deployments(
             for deployment in response.json()
         ]
     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
     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
     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
     except Exception as 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", "")
         invitation_code = resp_json.get("code", "")
     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:
-        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:
-        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:
-        console.debug(f"Server response format unexpected: {ke}")
+        console.error(f"Server response format unexpected: {ke}")
     except Exception:
-        console.debug("Unexpected errors: {ex}")
+        console.error("Unexpected errors: {ex}")
 
     return access_token, invitation_code
 
@@ -608,7 +608,7 @@ def poll_backend(backend_url: str) -> bool:
     """
     try:
         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()
         return True
     except httpx.HTTPError:
@@ -626,7 +626,7 @@ def poll_frontend(frontend_url: str) -> bool:
     """
     try:
         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()
         return True
     except httpx.HTTPError:
@@ -664,13 +664,13 @@ def delete_deployment(key: str):
         response.raise_for_status()
 
     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
     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
     except Exception as ex:
-        console.debug(f"Unexpected errors {ex}.")
+        console.error(f"Unexpected errors {ex}.")
         raise Exception("internal errors") from ex
 
 
@@ -755,7 +755,7 @@ def get_deployment_status(key: str) -> DeploymentStatusResponse:
             ),
         )
     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
 
 
@@ -772,7 +772,7 @@ def convert_to_local_time(iso_timestamp: str) -> str:
         local_dt = datetime.fromisoformat(iso_timestamp).astimezone()
         return local_dt.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
     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
 
 
@@ -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.
 
     Args:
@@ -1051,6 +1051,9 @@ async def display_deploy_milestones(key: str, from_iso_timestamp: datetime):
     Raises:
         ValueError: If a non-empty key is not provided.
         Exception: If the user is not authenticated.
+
+    Returns:
+        False if server reports back failure, True otherwise.
     """
     if not key:
         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(
-                        msg in row_json["message"].lower()
-                        for msg in END_OF_DEPLOYMENT_MESSAGES
-                    ):
+                    server_message = row_json["message"].lower()
+                    if "fail" in server_message:
+                        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(
                             "Received end of deployment message, stop event message streaming"
                         )
-                        return
+                        return True
                 else:
                     console.debug("Server responded, no new events yet, this is normal")
     except Exception as ex:
         console.debug(f"Unable to get more deployment events due to {ex}.")
+    return False
 
 
 def wait_for_server_to_pick_up_request():
@@ -1141,16 +1148,16 @@ def get_regions() -> list[dict]:
         response.raise_for_status()
         response_json = response.json()
         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 []
         if (
             response_json
             and response_json[0] is not None
             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 response_json
     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 []