dayhoff-tools 1.13.13__py3-none-any.whl → 1.13.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dayhoff_tools/cli/github_commands.py +286 -0
- dayhoff_tools/cli/main.py +2 -0
- {dayhoff_tools-1.13.13.dist-info → dayhoff_tools-1.13.14.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.13.13.dist-info → dayhoff_tools-1.13.14.dist-info}/RECORD +6 -5
- {dayhoff_tools-1.13.13.dist-info → dayhoff_tools-1.13.14.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.13.13.dist-info → dayhoff_tools-1.13.14.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""CLI commands for GitHub authentication.
|
|
2
|
+
|
|
3
|
+
This module provides commands for authenticating with GitHub from within
|
|
4
|
+
development containers using the GitHub CLI (gh).
|
|
5
|
+
|
|
6
|
+
The implementation:
|
|
7
|
+
1. Wraps `gh auth login` with sensible defaults for devcontainer environments
|
|
8
|
+
2. Automatically configures git to use GitHub CLI for credential management
|
|
9
|
+
3. Uses HTTPS protocol and device flow authentication (works in headless envs)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from typing import List, Tuple
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
|
|
19
|
+
# --- Configuration ---
|
|
20
|
+
# OAuth scopes to request during login:
|
|
21
|
+
# - repo: Full access to private/public repositories
|
|
22
|
+
# - read:org: Read-only access to organization membership
|
|
23
|
+
# - workflow: Ability to update GitHub Actions workflow files
|
|
24
|
+
GITHUB_SCOPES = "repo,read:org,workflow"
|
|
25
|
+
GITHUB_PROTOCOL = "https"
|
|
26
|
+
|
|
27
|
+
# --- Color constants for formatted output ---
|
|
28
|
+
RED = "\033[0;31m"
|
|
29
|
+
GREEN = "\033[0;32m"
|
|
30
|
+
YELLOW = "\033[0;33m"
|
|
31
|
+
BLUE = "\033[0;36m"
|
|
32
|
+
NC = "\033[0m" # No Color
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# --- Helper Functions ---
|
|
36
|
+
def _find_executable(name: str) -> str:
|
|
37
|
+
"""Find the full path to an executable in PATH."""
|
|
38
|
+
path = shutil.which(name)
|
|
39
|
+
if not path:
|
|
40
|
+
raise FileNotFoundError(
|
|
41
|
+
f"{name} command not found. Please ensure it's installed."
|
|
42
|
+
)
|
|
43
|
+
return path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _run_command(
|
|
47
|
+
cmd_list: List[str],
|
|
48
|
+
capture: bool = False,
|
|
49
|
+
check: bool = True,
|
|
50
|
+
suppress_output: bool = False,
|
|
51
|
+
) -> Tuple[int, str, str]:
|
|
52
|
+
"""Run a command and return its result.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
cmd_list: List of command arguments
|
|
56
|
+
capture: Whether to capture output
|
|
57
|
+
check: Whether to raise on non-zero exit code
|
|
58
|
+
suppress_output: Whether to hide output even if not captured
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Tuple of (return_code, stdout_str, stderr_str)
|
|
62
|
+
"""
|
|
63
|
+
stdout_opt = (
|
|
64
|
+
subprocess.PIPE if capture else subprocess.DEVNULL if suppress_output else None
|
|
65
|
+
)
|
|
66
|
+
stderr_opt = (
|
|
67
|
+
subprocess.PIPE if capture else subprocess.DEVNULL if suppress_output else None
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
result = subprocess.run(
|
|
72
|
+
cmd_list, stdout=stdout_opt, stderr=stderr_opt, check=check, text=True
|
|
73
|
+
)
|
|
74
|
+
return (
|
|
75
|
+
result.returncode,
|
|
76
|
+
result.stdout if capture else "",
|
|
77
|
+
result.stderr if capture else "",
|
|
78
|
+
)
|
|
79
|
+
except subprocess.CalledProcessError as e:
|
|
80
|
+
if capture:
|
|
81
|
+
return (e.returncode, e.stdout or "", e.stderr or "")
|
|
82
|
+
return (e.returncode, "", "")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _is_gh_authenticated() -> bool:
|
|
86
|
+
"""Check if GitHub CLI is authenticated.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
True if `gh auth status` succeeds, False otherwise.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
gh_path = _find_executable("gh")
|
|
93
|
+
returncode, _, _ = _run_command(
|
|
94
|
+
[gh_path, "auth", "status"],
|
|
95
|
+
capture=True,
|
|
96
|
+
check=False,
|
|
97
|
+
suppress_output=True,
|
|
98
|
+
)
|
|
99
|
+
return returncode == 0
|
|
100
|
+
except FileNotFoundError:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _get_gh_user() -> str:
|
|
105
|
+
"""Get the currently authenticated GitHub username.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
The username or 'Not authenticated' if not logged in.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
gh_path = _find_executable("gh")
|
|
112
|
+
returncode, stdout, _ = _run_command(
|
|
113
|
+
[gh_path, "auth", "status", "--show-token"],
|
|
114
|
+
capture=True,
|
|
115
|
+
check=False,
|
|
116
|
+
suppress_output=True,
|
|
117
|
+
)
|
|
118
|
+
if returncode != 0:
|
|
119
|
+
return "Not authenticated"
|
|
120
|
+
|
|
121
|
+
# Parse the output to find the logged in account
|
|
122
|
+
# Format: "Logged in to github.com account username (keyring)"
|
|
123
|
+
for line in stdout.split("\n"):
|
|
124
|
+
if "Logged in to" in line and "account" in line:
|
|
125
|
+
parts = line.split("account")
|
|
126
|
+
if len(parts) > 1:
|
|
127
|
+
# Extract username from "account username (keyring)" or similar
|
|
128
|
+
user_part = parts[1].strip().split()[0]
|
|
129
|
+
return user_part
|
|
130
|
+
return "Unknown"
|
|
131
|
+
except FileNotFoundError:
|
|
132
|
+
return "gh CLI not found"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _get_gh_scopes() -> str:
|
|
136
|
+
"""Get the OAuth scopes for the current GitHub authentication.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Comma-separated list of scopes or 'Unknown' if not available.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
gh_path = _find_executable("gh")
|
|
143
|
+
returncode, stdout, _ = _run_command(
|
|
144
|
+
[gh_path, "auth", "status"],
|
|
145
|
+
capture=True,
|
|
146
|
+
check=False,
|
|
147
|
+
suppress_output=True,
|
|
148
|
+
)
|
|
149
|
+
if returncode != 0:
|
|
150
|
+
return "N/A"
|
|
151
|
+
|
|
152
|
+
# Parse output for scopes - format varies by gh version
|
|
153
|
+
# Look for lines containing "Token scopes:" or similar
|
|
154
|
+
for line in stdout.split("\n"):
|
|
155
|
+
if "scopes" in line.lower():
|
|
156
|
+
# Extract everything after the colon
|
|
157
|
+
if ":" in line:
|
|
158
|
+
scopes = line.split(":", 1)[1].strip()
|
|
159
|
+
return scopes if scopes else "none"
|
|
160
|
+
return "Unknown"
|
|
161
|
+
except FileNotFoundError:
|
|
162
|
+
return "N/A"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# --- Typer Application ---
|
|
166
|
+
gh_app = typer.Typer(help="Manage GitHub authentication using the gh CLI.")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@gh_app.command("status")
|
|
170
|
+
def gh_status():
|
|
171
|
+
"""Show current GitHub authentication status."""
|
|
172
|
+
print(f"{BLUE}--- GitHub Authentication Status ---{NC}")
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
gh_path = _find_executable("gh")
|
|
176
|
+
except FileNotFoundError:
|
|
177
|
+
print(f"{RED}Error: GitHub CLI (gh) is not installed.{NC}")
|
|
178
|
+
print(
|
|
179
|
+
f"Install it with: {YELLOW}brew install gh{NC} (Mac) or see https://cli.github.com"
|
|
180
|
+
)
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
if _is_gh_authenticated():
|
|
184
|
+
user = _get_gh_user()
|
|
185
|
+
print(f" Status: {GREEN}Authenticated{NC}")
|
|
186
|
+
print(f" User: {GREEN}{user}{NC}")
|
|
187
|
+
|
|
188
|
+
# Show detailed status
|
|
189
|
+
print(f"\n{BLUE}Detailed status:{NC}")
|
|
190
|
+
_run_command([gh_path, "auth", "status"], check=False)
|
|
191
|
+
else:
|
|
192
|
+
print(f" Status: {RED}Not authenticated{NC}")
|
|
193
|
+
print(f"\nTo authenticate, run:")
|
|
194
|
+
print(f" {YELLOW}dh gh login{NC}")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@gh_app.command("login")
|
|
198
|
+
def gh_login():
|
|
199
|
+
"""Authenticate with GitHub and configure git credential helper.
|
|
200
|
+
|
|
201
|
+
This command:
|
|
202
|
+
1. Authenticates with GitHub using device flow (works in headless environments)
|
|
203
|
+
2. Requests scopes: repo, read:org, workflow
|
|
204
|
+
3. Configures git to use the GitHub CLI for credential management
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
gh_path = _find_executable("gh")
|
|
208
|
+
except FileNotFoundError:
|
|
209
|
+
print(f"{RED}Error: GitHub CLI (gh) is not installed.{NC}")
|
|
210
|
+
print(
|
|
211
|
+
f"Install it with: {YELLOW}brew install gh{NC} (Mac) or see https://cli.github.com"
|
|
212
|
+
)
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
# Step 1: Authenticate with GitHub
|
|
216
|
+
print(f"{BLUE}Authenticating with GitHub...{NC}")
|
|
217
|
+
print(f"{YELLOW}Requesting scopes: {GITHUB_SCOPES}{NC}")
|
|
218
|
+
print(f"{YELLOW}Using protocol: {GITHUB_PROTOCOL}{NC}")
|
|
219
|
+
print()
|
|
220
|
+
|
|
221
|
+
login_cmd = [
|
|
222
|
+
gh_path,
|
|
223
|
+
"auth",
|
|
224
|
+
"login",
|
|
225
|
+
"--web", # Use device flow (works in devcontainers)
|
|
226
|
+
"--git-protocol",
|
|
227
|
+
GITHUB_PROTOCOL,
|
|
228
|
+
"--scopes",
|
|
229
|
+
GITHUB_SCOPES,
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
returncode, _, _ = _run_command(login_cmd, capture=False, check=False)
|
|
233
|
+
|
|
234
|
+
if returncode != 0:
|
|
235
|
+
print(f"\n{RED}Authentication failed. Please check the output above.{NC}")
|
|
236
|
+
sys.exit(1)
|
|
237
|
+
|
|
238
|
+
# Step 2: Configure git credential helper
|
|
239
|
+
print(f"\n{BLUE}Configuring git to use GitHub CLI for credentials...{NC}")
|
|
240
|
+
setup_cmd = [gh_path, "auth", "setup-git"]
|
|
241
|
+
setup_rc, _, setup_err = _run_command(setup_cmd, capture=True, check=False)
|
|
242
|
+
|
|
243
|
+
if setup_rc != 0:
|
|
244
|
+
print(
|
|
245
|
+
f"{YELLOW}Warning: Failed to configure git credential helper: {setup_err}{NC}"
|
|
246
|
+
)
|
|
247
|
+
print(f"You may need to run manually: {YELLOW}gh auth setup-git{NC}")
|
|
248
|
+
else:
|
|
249
|
+
print(f"{GREEN}Git credential helper configured.{NC}")
|
|
250
|
+
|
|
251
|
+
# Step 3: Show final status
|
|
252
|
+
print(f"\n{GREEN}GitHub authentication complete!{NC}")
|
|
253
|
+
print(f"\n{BLUE}--- Current Status ---{NC}")
|
|
254
|
+
gh_status()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@gh_app.command("logout")
|
|
258
|
+
def gh_logout():
|
|
259
|
+
"""Log out from GitHub and clear credentials.
|
|
260
|
+
|
|
261
|
+
This removes the GitHub authentication token and unconfigures
|
|
262
|
+
the git credential helper.
|
|
263
|
+
"""
|
|
264
|
+
try:
|
|
265
|
+
gh_path = _find_executable("gh")
|
|
266
|
+
except FileNotFoundError:
|
|
267
|
+
print(f"{RED}Error: GitHub CLI (gh) is not installed.{NC}")
|
|
268
|
+
sys.exit(1)
|
|
269
|
+
|
|
270
|
+
if not _is_gh_authenticated():
|
|
271
|
+
print(f"{YELLOW}Not currently authenticated with GitHub.{NC}")
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
print(f"{BLUE}Logging out from GitHub...{NC}")
|
|
275
|
+
|
|
276
|
+
# Log out from github.com
|
|
277
|
+
logout_cmd = [gh_path, "auth", "logout", "--hostname", "github.com"]
|
|
278
|
+
returncode, _, _ = _run_command(logout_cmd, capture=False, check=False)
|
|
279
|
+
|
|
280
|
+
if returncode != 0:
|
|
281
|
+
print(f"{RED}Logout may have failed. Check the output above.{NC}")
|
|
282
|
+
sys.exit(1)
|
|
283
|
+
|
|
284
|
+
print(f"\n{GREEN}Successfully logged out from GitHub.{NC}")
|
|
285
|
+
print(f"\n{BLUE}To log back in:{NC}")
|
|
286
|
+
print(f" {YELLOW}dh gh login{NC}")
|
dayhoff_tools/cli/main.py
CHANGED
|
@@ -5,6 +5,7 @@ from importlib.metadata import PackageNotFoundError, version
|
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
from dayhoff_tools.cli.cloud_commands import aws_app, gcp_app
|
|
8
|
+
from dayhoff_tools.cli.github_commands import gh_app
|
|
8
9
|
from dayhoff_tools.cli.engine1 import engine_app, studio_app
|
|
9
10
|
from dayhoff_tools.cli.utility_commands import (
|
|
10
11
|
add_dependency,
|
|
@@ -67,6 +68,7 @@ app.command("wimport")(import_from_warehouse_typer)
|
|
|
67
68
|
# Cloud commands
|
|
68
69
|
app.add_typer(gcp_app, name="gcp", help="Manage GCP authentication and impersonation.")
|
|
69
70
|
app.add_typer(aws_app, name="aws", help="Manage AWS SSO authentication.")
|
|
71
|
+
app.add_typer(gh_app, name="gh", help="Manage GitHub authentication.")
|
|
70
72
|
|
|
71
73
|
# Engine and Studio commands (original implementation)
|
|
72
74
|
app.add_typer(engine_app, name="engine", help="Manage compute engines for development.")
|
|
@@ -25,7 +25,8 @@ dayhoff_tools/cli/engines_studios/simulators/simulator_utils.py,sha256=HA08pIMJW
|
|
|
25
25
|
dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py,sha256=ntizeR0BJLdJOwCRBKPajc2xT-BL7SNnONxfgxXDgr8,11609
|
|
26
26
|
dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py,sha256=6WvpnRawJVaQf_H81zuR1_66igRRVxPxjAt8e69xjp4,5394
|
|
27
27
|
dayhoff_tools/cli/engines_studios/studio_commands.py,sha256=4ul6i8HDHZTffavu1y_j4kwvsDNvjMvYMWbZGXN8nKY,25597
|
|
28
|
-
dayhoff_tools/cli/
|
|
28
|
+
dayhoff_tools/cli/github_commands.py,sha256=pfrxI68LObGm_gtPlQN-gHPahHV4l9k9T4GqO99NNL0,8948
|
|
29
|
+
dayhoff_tools/cli/main.py,sha256=6DLwNscPJccv3-07vnOnwxz3Vgbl7lRazFenZ66mYls,7264
|
|
29
30
|
dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
|
|
30
31
|
dayhoff_tools/cli/utility_commands.py,sha256=e2P4dCCtoqMUGNyb0lFBZ6GZpl5Zslm1qqE5qIvsy38,50765
|
|
31
32
|
dayhoff_tools/deployment/base.py,sha256=uZnFvnPQx6pH_HmJbdThweAs3BrxMaDohpE3iX_-yk4,18377
|
|
@@ -48,7 +49,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
|
|
|
48
49
|
dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
|
|
49
50
|
dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
|
|
50
51
|
dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
|
|
51
|
-
dayhoff_tools-1.13.
|
|
52
|
-
dayhoff_tools-1.13.
|
|
53
|
-
dayhoff_tools-1.13.
|
|
54
|
-
dayhoff_tools-1.13.
|
|
52
|
+
dayhoff_tools-1.13.14.dist-info/METADATA,sha256=pIzquyAaZbv2V8ZCtD0j3B7EZgb9f_YwZDzIIbCr6pI,2981
|
|
53
|
+
dayhoff_tools-1.13.14.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
54
|
+
dayhoff_tools-1.13.14.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
|
55
|
+
dayhoff_tools-1.13.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|