Skip to content

Commit 85a6d99

Browse files
Cleanup
1 parent 297f5ef commit 85a6d99

2 files changed

Lines changed: 276 additions & 194 deletions

File tree

src/fastapi_cloud_cli/commands/setup_ci.py

Lines changed: 60 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import logging
32
import re
43
import subprocess
@@ -14,53 +13,44 @@
1413

1514
logger = logging.getLogger(__name__)
1615

17-
TOKEN_NAME_PREFIX = "GitHub Actions"
1816
TOKEN_EXPIRES_DAYS = 365
1917
DEFAULT_WORKFLOW_PATH = Path(".github/workflows/deploy.yml")
2018

2119

22-
def _get_origin() -> str:
23-
try:
24-
result = subprocess.run(
25-
["git", "config", "--get", "remote.origin.url"],
26-
capture_output=True,
27-
text=True,
28-
check=True,
29-
)
30-
return result.stdout.strip()
31-
except subprocess.CalledProcessError:
32-
logger.error(
33-
"Error retrieving git remote origin URL. Make sure you're in a git repository with a remote origin set."
34-
)
35-
raise typer.Exit(1) from None
36-
37-
3820
def _repo_slug_from_origin(origin: str) -> str | None:
3921
"""Extract 'owner/repo' from a GitHub remote URL."""
22+
# Handles URLs like: git@github.com:owner/repo.git or https://github.com/owner/repo.git
4023
match = re.search(r"github\.com[:/](.+?)(?:\.git)?$", origin)
4124
return match.group(1) if match else None
4225

4326

4427
def _check_gh_cli_installed() -> bool:
28+
"""Check if the GitHub CLI (gh) is installed and available."""
4529
try:
4630
subprocess.run(["gh", "--version"], capture_output=True, text=True, check=True)
4731
return True
4832
except (subprocess.CalledProcessError, FileNotFoundError):
4933
return False
5034

5135

52-
def _token_name(repo_slug: str) -> str:
53-
return f"{TOKEN_NAME_PREFIX}{repo_slug}"
36+
def _get_remote_origin() -> str:
37+
"""Get the remote origin URL of the Git repository."""
38+
result = subprocess.run(
39+
["git", "config", "--get", "remote.origin.url"],
40+
capture_output=True,
41+
text=True,
42+
check=True,
43+
)
44+
return result.stdout.strip()
5445

5546

56-
def _find_existing_token(client: APIClient, app_id: str, token_name: str) -> str | None:
57-
"""Return the token ID if a token with the given name already exists."""
58-
response = client.get(f"/apps/{app_id}/tokens")
59-
response.raise_for_status()
60-
for token in response.json()["data"]:
61-
if token["name"] == token_name:
62-
return str(token["id"])
63-
return None
47+
def _set_github_secret(name: str, value: str) -> None:
48+
"""Set a GitHub Actions secret via the gh CLI."""
49+
subprocess.run(
50+
["gh", "secret", "set", name, "--body", value],
51+
capture_output=True,
52+
check=True,
53+
)
6454

6555

6656
def _create_or_regenerate_token(
@@ -71,7 +61,14 @@ def _create_or_regenerate_token(
7161
Returns (token_data, regenerated).
7262
"""
7363
with APIClient() as client:
74-
existing_id = _find_existing_token(client, app_id, token_name)
64+
existing_id = None
65+
66+
response = client.get(f"/apps/{app_id}/tokens")
67+
response.raise_for_status()
68+
for token in response.json()["data"]:
69+
if token["name"] == token_name:
70+
existing_id = token["id"]
71+
break
7572

7673
if existing_id:
7774
response = client.post(
@@ -93,36 +90,20 @@ def _create_or_regenerate_token(
9390

9491

9592
def _get_default_branch() -> str:
96-
if not _check_gh_cli_installed():
97-
return "main"
93+
"""Get the default branch of the Git repository."""
9894
try:
9995
result = subprocess.run(
100-
["gh", "repo", "view", "--json", "defaultBranchRef"],
96+
["git", "symbolic-ref", "refs/remotes/origin/HEAD"],
10197
capture_output=True,
10298
text=True,
10399
check=True,
104100
)
105-
106-
repo_info = json.loads(result.stdout)
107-
return str(repo_info["defaultBranchRef"]["name"])
108-
except (subprocess.CalledProcessError, KeyError, json.JSONDecodeError):
101+
return result.stdout.strip().split("/")[-1]
102+
except subprocess.CalledProcessError:
109103
return "main"
110104

111105

112-
def _set_github_secret(secret_name: str, secret_value: str) -> None:
113-
try:
114-
subprocess.run(
115-
["gh", "secret", "set", secret_name, "--body", secret_value],
116-
capture_output=True,
117-
check=True,
118-
)
119-
except (subprocess.CalledProcessError, FileNotFoundError) as e:
120-
logger.error(f"Error setting GitHub secret: {e}")
121-
122-
123-
def _write_workflow_file(
124-
branch: str, workflow_path: Path = DEFAULT_WORKFLOW_PATH
125-
) -> str:
106+
def _write_workflow_file(branch: str, workflow_path: Path) -> None:
126107
workflow_content = f"""name: Deploy to FastAPI Cloud
127108
on:
128109
push:
@@ -140,7 +121,6 @@ def _write_workflow_file(
140121
"""
141122
workflow_path.parent.mkdir(parents=True, exist_ok=True)
142123
workflow_path.write_text(workflow_content)
143-
return str(workflow_path)
144124

145125

146126
def setup_ci(
@@ -150,12 +130,11 @@ def setup_ci(
150130
help="Path to the folder containing the app (defaults to current directory)"
151131
),
152132
] = None,
153-
branch: str = typer.Option(
154-
"main",
133+
branch: str | None = typer.Option(
134+
None,
155135
"--branch",
156136
"-b",
157-
help="Branch that triggers deploys",
158-
show_default=True,
137+
help="Branch that triggers deploys (defaults to the repo's default branch)",
159138
),
160139
secrets_only: bool = typer.Option(
161140
False,
@@ -208,7 +187,15 @@ def setup_ci(
208187
)
209188
raise typer.Exit(1)
210189

211-
origin = _get_origin()
190+
try:
191+
origin = _get_remote_origin()
192+
except subprocess.CalledProcessError:
193+
toolkit.print(
194+
"Error retrieving git remote origin URL. Make sure you're in a git repository with a remote origin set.",
195+
tag="error",
196+
)
197+
raise typer.Exit(1) from None
198+
212199
if "github.com" not in origin:
213200
toolkit.print(
214201
"Remote origin is not a GitHub repository. Please set up a GitHub repo and add it as the remote origin.",
@@ -217,12 +204,11 @@ def setup_ci(
217204
raise typer.Exit(1)
218205

219206
repo_slug = _repo_slug_from_origin(origin) or origin
207+
has_gh = _check_gh_cli_installed()
220208

221-
default_branch = _get_default_branch()
222-
if branch == "main" and default_branch != "main":
223-
branch = default_branch
209+
if not branch:
210+
branch = _get_default_branch()
224211

225-
# -- header --
226212
if dry_run:
227213
toolkit.print(
228214
"[yellow]This is a dry run — no changes will be made[/yellow]"
@@ -252,8 +238,7 @@ def setup_ci(
252238
toolkit.print(msg_workflow)
253239
return
254240

255-
# -- create deploy token --
256-
token_name = _token_name(repo_slug)
241+
token_name = f"GitHub Actions — {repo_slug}"
257242
toolkit.print("Generating deploy token...")
258243
toolkit.print_line()
259244
with (
@@ -269,14 +254,16 @@ def setup_ci(
269254

270255
toolkit.print_line()
271256

272-
# -- set github secrets --
273-
has_gh = _check_gh_cli_installed()
274257
if has_gh:
275258
toolkit.print(f"Setting repo secrets on [bold]{repo_slug}[/bold]")
276259
toolkit.print_line()
277260
with toolkit.progress(title="Setting repo secrets...") as progress:
278-
_set_github_secret("FASTAPI_CLOUD_TOKEN", token_data["value"])
279-
_set_github_secret("FASTAPI_CLOUD_APP_ID", app_config.app_id)
261+
try:
262+
_set_github_secret("FASTAPI_CLOUD_TOKEN", token_data["value"])
263+
_set_github_secret("FASTAPI_CLOUD_APP_ID", app_config.app_id)
264+
except (subprocess.CalledProcessError, FileNotFoundError):
265+
progress.set_error("Failed to set GitHub secrets via gh CLI.")
266+
raise typer.Exit(1) from None
280267
progress.log(msg_secrets)
281268
else:
282269
secrets_url = f"https://github.com/{repo_slug}/settings/secrets/actions"
@@ -292,33 +279,32 @@ def setup_ci(
292279

293280
toolkit.print_line()
294281

295-
# -- write workflow file --
296282
if not secrets_only:
297283
if file:
298284
workflow_path = Path(f".github/workflows/{file}")
299285
else:
300286
workflow_path = DEFAULT_WORKFLOW_PATH
301287

288+
write_workflow = True
302289
if not file and workflow_path.exists():
303290
overwrite = toolkit.confirm(
304291
f"Workflow file [bold]{workflow_path}[/bold] already exists. Overwrite?",
305292
tag="workflow",
306293
default=False,
307294
)
308295
if not overwrite:
309-
new_name = typer.prompt(
310-
"Enter a new filename (or press Enter to skip)",
311-
default="",
312-
show_default=False,
313-
)
296+
new_name = toolkit.input(
297+
"Enter a new filename (without path) or leave blank to skip writing the workflow file:",
298+
tag="workflow",
299+
).strip()
314300
if new_name:
315301
workflow_path = Path(f".github/workflows/{new_name}")
316302
else:
317303
toolkit.print("Skipped writing workflow file.")
318304
toolkit.print_line()
319-
workflow_path = None # type: ignore[assignment]
305+
write_workflow = False
320306
toolkit.print_line()
321-
if workflow_path is not None:
307+
if write_workflow:
322308
msg_workflow = f"Wrote [bold]{workflow_path}[/bold] (branch: {branch})"
323309
with toolkit.progress(title="Writing workflow file...") as progress:
324310
_write_workflow_file(branch, workflow_path)

0 commit comments

Comments
 (0)