Coverage for src/fastapi_cloud_cli/commands/tokens/create.py: 100%
64 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-09 00:25 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-09 00:25 +0000
1from pathlib import Path 1abcdefg
2from typing import Annotated, Any, Literal 1abcdefg
4import typer 1abcdefg
5from pydantic import BaseModel, Field 1abcdefg
6from rich_toolkit import RichToolkit 1abcdefg
8from fastapi_cloud_cli.api import APIClient 1abcdefg
9from fastapi_cloud_cli.commands._auth import UserCommand, get_user_command_context 1abcdefg
10from fastapi_cloud_cli.commands.tokens._app import tokens_app 1abcdefg
11from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail 1abcdefg
12from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit 1abcdefg
13from fastapi_cloud_cli.utils.execution import JsonOutputOption 1abcdefg
15DEFAULT_EXPIRES_IN_DAYS = 365 1abcdefg
18class CreatedDeployToken(BaseModel): 1abcdefg
19 id: str 1abcdefg
20 name: str 1abcdefg
21 expired_at: str 1abcdefg
24class DeployTokenCreateAPIResponse(CreatedDeployToken): 1abcdefg
25 value: str 1abcdefg
28class StoredDeployTokenSecret(BaseModel): 1abcdefg
29 provider: Literal["file"] = "file" 1abcdefg
30 path: Path 1abcdefg
33class DeployTokenCreateOutput(BaseModel): 1abcdefg
34 app_id: str 1abcdefg
35 token: CreatedDeployToken 1abcdefg
36 stored_secret: StoredDeployTokenSecret 1abcdefg
37 output_file: Annotated[Path, Field(exclude=True)] 1abcdefg
40def _resolve_token_name(toolkit: FastAPIRichToolkit, *, name: str | None) -> str: 1abcdefg
41 if name is not None: 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I
42 return name 1oQvwpRxyqSzArTBCsUDEtVFGuWHI
44 if toolkit.mode == "json": 1kJXhKYlLZiM0mN1nO2jP3
45 toolkit.fail( 1XYZ0123
46 "missing_required_input",
47 "Deploy token name is required.",
48 hint="Pass --name to choose a deploy token name.",
49 )
51 return toolkit.input( 1kJhKlLiMmNnOjP
52 "What's the deploy token name?",
53 default="Deploy token",
54 bullet=False,
55 )
58def _resolve_output_file( 1abcdefg
59 toolkit: FastAPIRichToolkit, *, output_file: Path | None
60) -> Path:
61 if output_file is not None: 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I
62 return output_file 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I
64 toolkit.fail( 1456789!#$%'()*
65 "missing_required_input",
66 "Output file is required.",
67 hint="Pass --output-file to store the deploy token value.",
68 )
71def _create_deploy_token( 1abcdefg
72 client: APIClient, *, app_id: str, name: str, expires_in_days: int
73) -> DeployTokenCreateAPIResponse:
74 response = client.post( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
75 f"/apps/{app_id}/tokens",
76 json={"name": name, "expires_in_days": expires_in_days},
77 )
78 response.raise_for_status() 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
80 return DeployTokenCreateAPIResponse.model_validate(response.json()) 1okvwphxyqlzAriBCsmDEtnFGujHI
83def _write_token_value(output_file: Path, value: str) -> None: 1abcdefg
84 output_file.parent.mkdir(parents=True, exist_ok=True) 1okvwphxyqlzAriBCsmDEtnFGujHI
85 output_file.write_text(value, encoding="utf-8") 1okvwphxyqlzAriBCsmDEtnFGujHI
86 output_file.chmod(0o600) 1okvwphxyqlzAriBCsmDEtnFGujHI
89def _render_deploy_token_create_output( 1abhcdiefgj
90 data: DeployTokenCreateOutput, toolkit: RichToolkit
91) -> None:
92 toolkit.print(f"Created deploy token [bold]{data.token.name}[/bold]", bullet=False) 1okphqlrismtnuj
93 toolkit.print( 1okphqlrismtnuj
94 f"Stored deploy token value in [bold]{data.output_file}[/bold]",
95 bullet=False,
96 )
99@tokens_app.command("create", cls=UserCommand) 1abcdefg
100def create_token( 1ab+cd,efg-
101 ctx: typer.Context,
102 app_id: Annotated[
103 str | None,
104 typer.Option(
105 "--app-id",
106 help="ID of the app whose deploy token should be created.",
107 ),
108 ] = None,
109 name: Annotated[
110 str | None,
111 typer.Option(
112 "--name",
113 help="Name of the deploy token to create.",
114 ),
115 ] = None,
116 expires_in_days: Annotated[
117 int,
118 typer.Option(
119 "--expires-in-days",
120 help="Number of days before the deploy token expires.",
121 min=1,
122 ),
123 ] = DEFAULT_EXPIRES_IN_DAYS,
124 output_file: Annotated[
125 Path | None,
126 typer.Option(
127 "--output-file",
128 help="File path where the deploy token value should be stored.",
129 ),
130 ] = None,
131 json_output: JsonOutputOption = False,
132) -> Any:
133 """
134 Create a deploy token for an app.
135 """
137 toolkit = get_user_command_context(ctx).toolkit 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I
139 target_app_id = resolve_app_id_or_fail(toolkit, app_id=app_id) 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I
141 toolkit.print_title("deploy tokens") 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I
142 toolkit.print_line() 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I
144 output_file = _resolve_output_file(toolkit, output_file=output_file) 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I
145 name_needs_prompt = name is None 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I
146 name = _resolve_token_name(toolkit, name=name) 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I
147 if name_needs_prompt: 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
148 toolkit.print_line() 1kJhKlLiMmNnOjP
150 with APIClient() as client: 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
151 with toolkit.progress( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
152 title="Creating deploy token",
153 transient=True,
154 ) as progress:
155 with client.handle_http_errors( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
156 progress,
157 default_message="Error creating deploy token. Please try again later.",
158 not_found_message="App not found.",
159 toolkit=toolkit,
160 ):
161 token = _create_deploy_token( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI
162 client,
163 app_id=target_app_id,
164 name=name,
165 expires_in_days=expires_in_days,
166 )
168 _write_token_value(output_file, token.value) 1okvwphxyqlzAriBCsmDEtnFGujHI
170 toolkit.success( 1okvwphxyqlzAriBCsmDEtnFGujHI
171 DeployTokenCreateOutput(
172 app_id=target_app_id,
173 token=CreatedDeployToken.model_validate(token),
174 stored_secret=StoredDeployTokenSecret(path=output_file),
175 output_file=output_file,
176 ),
177 render_output=_render_deploy_token_create_output,
178 )