Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/fastapi_cloud_cli/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@ def remove_custom_domain(self, *, app_id: str, domain_id: str) -> None:
response = self.delete(f"/apps/{app_id}/custom-domains/{domain_id}")
response.raise_for_status()

def restart_custom_domain_setup(
self,
*,
app_id: str,
domain_id: str,
) -> CustomDomain:
response = self.post(f"/apps/{app_id}/custom-domains/{domain_id}/restart-setup")
response.raise_for_status()

return CustomDomain.model_validate(response.json())

@attempts(STREAM_LOGS_MAX_RETRIES, STREAM_LOGS_TIMEOUT)
def stream_build_logs(
self, deployment_id: str, *, follow: bool = True
Expand Down
2 changes: 2 additions & 0 deletions src/fastapi_cloud_cli/commands/domains/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from fastapi_cloud_cli.commands.domains.get import get_domain
from fastapi_cloud_cli.commands.domains.list import list_domains
from fastapi_cloud_cli.commands.domains.remove import remove_domain
from fastapi_cloud_cli.commands.domains.restart import restart_domain

domains_app = typer.Typer(
no_args_is_help=True,
Expand All @@ -13,5 +14,6 @@
domains_app.command("get")(get_domain)
domains_app.command("list")(list_domains)
domains_app.command("remove")(remove_domain)
domains_app.command("restart")(restart_domain)

__all__ = ["domains_app"]
157 changes: 157 additions & 0 deletions src/fastapi_cloud_cli/commands/domains/restart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from typing import Annotated, Any

import typer
from pydantic import BaseModel, Field
from rich_toolkit import RichToolkit

from fastapi_cloud_cli.api import APIClient, CustomDomain
from fastapi_cloud_cli.commands.domains._shared import (
_find_custom_domain,
_select_custom_domain,
)
from fastapi_cloud_cli.commands.domains.rendering import render_custom_domain_details
from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail
from fastapi_cloud_cli.utils.auth import Identity
from fastapi_cloud_cli.utils.cli import get_rich_toolkit
from fastapi_cloud_cli.utils.execution import JsonOutputOption


class CustomDomainRestartOutput(BaseModel):
app_id: str
domain: CustomDomain
show_title: Annotated[bool, Field(exclude=True)] = True


def _render_custom_domain_restart_output(
data: CustomDomainRestartOutput,
toolkit: RichToolkit,
) -> None:
if data.show_title:
toolkit.print_title("custom domains")
toolkit.print_line()

toolkit.print(
f"Restarted verification for [bold]{data.domain.name}[/bold]",
emoji="🐔",
)
toolkit.print_line()
render_custom_domain_details(data.domain, toolkit)
toolkit.print_line()
toolkit.print(
"[dim]hint: Run `fastapi cloud domains get "
f"{data.domain.name}` to check progress.[/dim]"
)


def restart_domain(
domain: Annotated[
str | None,
typer.Argument(
help="Hostname or ID of the custom domain whose setup should restart.",
),
] = None,
app_id: Annotated[
str | None,
typer.Option(
"--app-id",
help="ID of the app that owns the custom domain.",
),
] = None,
json_output: JsonOutputOption = False,
) -> Any:
"""
Restart failed custom domain setup for an app.
"""
identity = Identity()

with get_rich_toolkit(json_output=json_output) as toolkit:
if not identity.is_logged_in():
toolkit.fail(
"not_logged_in",
"No credentials found.",
hint="Run `fastapi cloud login`.",
)

app_id = resolve_app_id_or_fail(toolkit, app_id=app_id)
domain_was_provided = domain is not None

if domain is None and toolkit.mode == "json":
toolkit.fail(
"missing_required_input",
"Custom domain is required.",
hint="Pass DOMAIN to choose a custom domain.",
)

with APIClient() as client:
with (
toolkit.progress(
title="Fetching custom domains",
transient=True,
) as progress,
client.handle_http_errors(
progress,
default_message=(
"Error fetching custom domains. Please try again later."
),
not_found_message="App not found.",
toolkit=toolkit,
),
):
domains = client.get_custom_domains(app_id=app_id).data

selected_domain: CustomDomain | None
if domain is None:
toolkit.print_title("custom domains")
toolkit.print_line()
failed_domains = [domain for domain in domains if domain.setup_failed]

if not failed_domains:
toolkit.print("No failed custom domains found.", bullet=False)
return

selected_domain = _select_custom_domain(
toolkit,
failed_domains,
prompt="Select the custom domain to restart:",
)
toolkit.print_line()
else:
selected_domain = _find_custom_domain(domains, domain)
if selected_domain is None:
toolkit.fail(
"not_found",
f"Custom domain {domain} not found.",
hint=(
"Run `fastapi cloud domains list` to see available "
"custom domains."
),
)

assert selected_domain is not None
with (
toolkit.progress(
title="Restarting custom domain setup",
transient=True,
) as progress,
client.handle_http_errors(
progress,
default_message=(
"Error restarting custom domain setup. Please try again later."
),
not_found_message="Custom domain not found.",
toolkit=toolkit,
),
):
restarted_domain = client.restart_custom_domain_setup(
app_id=app_id,
domain_id=selected_domain.id,
)

toolkit.success(
CustomDomainRestartOutput(
app_id=app_id,
domain=restarted_domain,
show_title=domain_was_provided,
),
render_output=_render_custom_domain_restart_output,
)
Loading