-
Notifications
You must be signed in to change notification settings - Fork 11
✨ Handle archive size limit #305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,25 @@ | ||
| import logging | ||
| import tempfile | ||
| from pathlib import Path | ||
| from typing import Annotated, Any, cast | ||
| from typing import Annotated, Any, NoReturn, cast | ||
|
|
||
| import typer | ||
| from pydantic import BaseModel | ||
| from rich_toolkit.progress import Progress | ||
|
|
||
| from fastapi_cloud_cli.commands.deploy.archive import _get_large_files, archive | ||
| from fastapi_cloud_cli.commands.deploy.cloud import ( | ||
| AppResponse, | ||
| ArchiveTooLargeError, | ||
| CreateDeploymentResponse, | ||
| _create_deployment, | ||
| _get_app, | ||
| ) | ||
| from fastapi_cloud_cli.commands.deploy.configure import _configure_app | ||
| from fastapi_cloud_cli.commands.deploy.upload import _cancel_upload, _upload_deployment | ||
| from fastapi_cloud_cli.commands.deploy.upload import ( | ||
| _cancel_upload, | ||
| _upload_deployment, | ||
| ) | ||
| from fastapi_cloud_cli.commands.deploy.wait import _wait_for_deployment | ||
| from fastapi_cloud_cli.commands.login import _interactive_login | ||
| from fastapi_cloud_cli.utils.api import APIClient, DeploymentStatus | ||
|
|
@@ -47,6 +52,18 @@ def _get_deploy_output(deployment: CreateDeploymentResponse) -> DeployOutput: | |
| ) | ||
|
|
||
|
|
||
| def _fail_archive_too_large( | ||
| toolkit: FastAPIRichToolkit, progress: Progress, message: str | ||
| ) -> NoReturn: | ||
| hint = "You can exclude files from the deployment with a .fastapicloudignore file." | ||
|
|
||
| if toolkit.mode == "json": | ||
| toolkit.fail("invalid_input", message, hint=hint) | ||
|
|
||
| progress.set_error(f"{message}\n\n[dim]hint: {hint}[/]") | ||
| raise typer.Exit(1) from None | ||
|
patrick91 marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| def _get_large_file_warnings( | ||
| large_files: list[tuple[Path, int]], | ||
| *, | ||
|
|
@@ -313,6 +330,7 @@ def deploy( | |
| logger.debug("Creating archive for deployment") | ||
| archive_path = Path(temp_dir) / "archive.tar" | ||
| archive(path_to_deploy, archive_path) | ||
| archive_size = archive_path.stat().st_size | ||
|
|
||
| with ( | ||
| toolkit.progress( | ||
|
|
@@ -324,7 +342,15 @@ def deploy( | |
| client.handle_http_errors(progress, toolkit=toolkit), | ||
| ): | ||
| logger.debug("Creating deployment for app: %s", app.id) | ||
| deployment = _create_deployment(client=client, app_id=app.id) | ||
|
|
||
| try: | ||
| deployment = _create_deployment( | ||
| client=client, | ||
| app_id=app.id, | ||
| archive_size_bytes=archive_size, | ||
| ) | ||
| except ArchiveTooLargeError as e: | ||
| _fail_archive_too_large(toolkit, progress, str(e)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see the comment above, with toolkit.fail we can probably remove this floating function |
||
|
|
||
| try: | ||
| progress.log( | ||
|
|
@@ -335,13 +361,17 @@ def deploy( | |
| fastapi_client=client, | ||
| deployment_id=deployment.id, | ||
| archive_path=archive_path, | ||
| archive_size=archive_size, | ||
| progress=progress, | ||
| ) | ||
|
|
||
| progress.log("Deployment uploaded successfully!") | ||
| except KeyboardInterrupt: | ||
| _cancel_upload(client=client, deployment_id=deployment.id) | ||
| raise | ||
| except ArchiveTooLargeError as e: | ||
| _cancel_upload(client=client, deployment_id=deployment.id) | ||
| _fail_archive_too_large(toolkit, progress, str(e)) | ||
|
|
||
| if will_wait: | ||
| logger.debug("Waiting for deployment to complete") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,16 @@ | ||
| import logging | ||
| import xml.etree.ElementTree as ET | ||
| from pathlib import Path | ||
| from typing import BinaryIO, cast | ||
|
|
||
| from httpx import Client | ||
| from httpx import Client, Response | ||
| from pydantic import BaseModel | ||
| from rich_toolkit.progress import Progress | ||
|
|
||
| from fastapi_cloud_cli.commands.deploy.cloud import CreateDeploymentResponse | ||
| from fastapi_cloud_cli.commands.deploy.cloud import ( | ||
| ArchiveTooLargeError, | ||
| CreateDeploymentResponse, | ||
| ) | ||
| from fastapi_cloud_cli.utils.api import APIClient | ||
| from fastapi_cloud_cli.utils.progress_file import ProgressFile | ||
|
|
||
|
|
@@ -39,13 +43,23 @@ def _format_size(size_in_bytes: int) -> str: | |
| return f"{size_in_bytes} bytes" | ||
|
|
||
|
|
||
| def _get_s3_error_code(response: Response) -> str | None: | ||
| """Extract the error code from an S3 XML error response.""" | ||
| try: | ||
| root = ET.fromstring(response.text) | ||
| except ET.ParseError: | ||
| return None | ||
|
|
||
| return root.findtext("Code") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not a big fan of having S3 specific things here, could we avoid it? (not sure if it is even worth handling this error directly, since it shouldn't ever happen) |
||
|
|
||
|
|
||
| def _upload_deployment( | ||
| fastapi_client: APIClient, | ||
| deployment_id: str, | ||
| archive_path: Path, | ||
| archive_size: int, | ||
| progress: Progress, | ||
| ) -> CreateDeploymentResponse: | ||
| archive_size = archive_path.stat().st_size | ||
| archive_size_str = _format_size(archive_size) | ||
|
|
||
| progress.log(f"Uploading deployment ({archive_size_str})...") | ||
|
|
@@ -79,6 +93,15 @@ def progress_callback(bytes_read: int) -> None: | |
| files={"file": cast(BinaryIO, archive_file_with_progress)}, | ||
| ) | ||
|
|
||
| if upload_response.is_error: | ||
| logger.debug("File upload failed with response: %s", upload_response.text) | ||
|
|
||
| if _get_s3_error_code(upload_response) == "EntityTooLarge": | ||
| raise ArchiveTooLargeError( | ||
| f"The deployment archive is {archive_size_str}, " | ||
| "which exceeds the maximum allowed size." | ||
| ) | ||
|
|
||
| upload_response.raise_for_status() | ||
| logger.debug("File upload completed successfully") | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.