dh-cli 0.4.2__tar.gz → 0.4.4__tar.gz
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.
- {dh_cli-0.4.2 → dh_cli-0.4.4}/PKG-INFO +1 -1
- {dh_cli-0.4.2 → dh_cli-0.4.4}/pyproject.toml +1 -1
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/deploy.py +15 -3
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/tf.py +18 -3
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/users.py +50 -9
- {dh_cli-0.4.2 → dh_cli-0.4.4}/tests/hz/test_users.py +48 -1
- {dh_cli-0.4.2 → dh_cli-0.4.4}/.gitignore +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/LICENSE +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/README.md +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/__init__.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/__init__.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/aws_batch.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/__init__.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/boltz.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/cancel.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/clean.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/embed_t5.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/finalize.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/list_jobs.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/local.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/logs.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/protmpnn.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/protmpnn_to_boltz.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/retry.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/status.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/submit.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/train.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/wait_for.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/fasta_utils.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/h5_utils.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/job_id.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/manifest.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/s3_transport.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/cloud_commands.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/codeartifact.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/__init__.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/api_client.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/auth.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/engine_commands.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/progress.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/ssh_config.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/studio_commands.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/github_commands.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/__init__.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/local.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/test.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/main.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/utility_commands.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/warehouse.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/tests/hz/test_init.py +0 -0
- {dh_cli-0.4.2 → dh_cli-0.4.4}/tests/hz/test_suites.py +0 -0
|
@@ -7,11 +7,21 @@ from dh_cli.hz import require_repo, run_script
|
|
|
7
7
|
deploy_app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _confirm_deploy(target: str, env: str) -> None:
|
|
11
|
+
color = typer.colors.RED if env == "prod" else typer.colors.BRIGHT_GREEN
|
|
12
|
+
env_styled = typer.style(env, fg=color, bold=True)
|
|
13
|
+
typer.echo(f"\n Deploying {target} to {env_styled}\n")
|
|
14
|
+
if not typer.confirm(" Continue?"):
|
|
15
|
+
raise typer.Abort()
|
|
16
|
+
typer.echo()
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
@deploy_app.command("api")
|
|
11
20
|
def deploy_api(
|
|
12
|
-
env: str = typer.Option("
|
|
21
|
+
env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
|
|
13
22
|
):
|
|
14
23
|
"""Build and deploy the Horizyn API server."""
|
|
24
|
+
_confirm_deploy("api", env)
|
|
15
25
|
repo = require_repo("horizyn-api")
|
|
16
26
|
script = repo / f"server/build-and-push-{env}.sh"
|
|
17
27
|
run_script(script, cwd=repo)
|
|
@@ -19,9 +29,10 @@ def deploy_api(
|
|
|
19
29
|
|
|
20
30
|
@deploy_app.command("docking")
|
|
21
31
|
def deploy_docking(
|
|
22
|
-
env: str = typer.Option("
|
|
32
|
+
env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
|
|
23
33
|
):
|
|
24
34
|
"""Build and deploy the docking service."""
|
|
35
|
+
_confirm_deploy("docking", env)
|
|
25
36
|
repo = require_repo("horizyn-api")
|
|
26
37
|
script = repo / f"docking_service/build-and-push-{env}.sh"
|
|
27
38
|
run_script(script, cwd=repo)
|
|
@@ -29,9 +40,10 @@ def deploy_docking(
|
|
|
29
40
|
|
|
30
41
|
@deploy_app.command("frontend")
|
|
31
42
|
def deploy_frontend(
|
|
32
|
-
env: str = typer.Option("
|
|
43
|
+
env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
|
|
33
44
|
):
|
|
34
45
|
"""Build and deploy the Horizyn frontend."""
|
|
46
|
+
_confirm_deploy("frontend", env)
|
|
35
47
|
repo = require_repo("horizyn-frontend")
|
|
36
48
|
script = repo / "scripts/deploy.sh"
|
|
37
49
|
run_script(script, args=[env], cwd=repo)
|
|
@@ -16,6 +16,15 @@ TF_MODULES = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def _confirm_tf(target: str, env: str) -> None:
|
|
20
|
+
color = typer.colors.RED if env == "prod" else typer.colors.BRIGHT_GREEN
|
|
21
|
+
env_styled = typer.style(env, fg=color, bold=True)
|
|
22
|
+
typer.echo(f"\n Terraform apply: {target} in {env_styled}\n")
|
|
23
|
+
if not typer.confirm(" Continue?"):
|
|
24
|
+
raise typer.Abort()
|
|
25
|
+
typer.echo()
|
|
26
|
+
|
|
27
|
+
|
|
19
28
|
def _run_tf(target: str, env: str, action: str) -> None:
|
|
20
29
|
repo = require_repo("blueprints")
|
|
21
30
|
module_path = TF_MODULES.get((target, env))
|
|
@@ -28,26 +37,32 @@ def _run_tf(target: str, env: str, action: str) -> None:
|
|
|
28
37
|
|
|
29
38
|
@tf_app.command("api")
|
|
30
39
|
def tf_api(
|
|
31
|
-
env: str = typer.Option("
|
|
40
|
+
env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
|
|
32
41
|
yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
|
|
33
42
|
):
|
|
34
43
|
"""Terraform apply for the Horizyn API server."""
|
|
44
|
+
if not yolo:
|
|
45
|
+
_confirm_tf("api", env)
|
|
35
46
|
_run_tf("api", env, "yolo" if yolo else "plan")
|
|
36
47
|
|
|
37
48
|
|
|
38
49
|
@tf_app.command("frontend")
|
|
39
50
|
def tf_frontend(
|
|
40
|
-
env: str = typer.Option("
|
|
51
|
+
env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
|
|
41
52
|
yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
|
|
42
53
|
):
|
|
43
54
|
"""Terraform apply for the Horizyn frontend."""
|
|
55
|
+
if not yolo:
|
|
56
|
+
_confirm_tf("frontend", env)
|
|
44
57
|
_run_tf("frontend", env, "yolo" if yolo else "plan")
|
|
45
58
|
|
|
46
59
|
|
|
47
60
|
@tf_app.command("docking")
|
|
48
61
|
def tf_docking(
|
|
49
|
-
env: str = typer.Option("
|
|
62
|
+
env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
|
|
50
63
|
yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
|
|
51
64
|
):
|
|
52
65
|
"""Terraform apply for the Boltz docking service."""
|
|
66
|
+
if not yolo:
|
|
67
|
+
_confirm_tf("docking", env)
|
|
53
68
|
_run_tf("docking", env, "yolo" if yolo else "plan")
|
|
@@ -40,9 +40,26 @@ def _format_csv(emails: set[str]) -> str:
|
|
|
40
40
|
return ",".join(sorted(emails))
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def compute_user_tiers(
|
|
44
|
+
allowed_emails: set[str],
|
|
45
|
+
alpha_emails: set[str],
|
|
46
|
+
admin_emails: set[str],
|
|
47
|
+
) -> tuple[set[str], set[str], set[str]]:
|
|
48
|
+
"""Derive beta set and find orphaned emails not on the allowed list.
|
|
49
|
+
|
|
50
|
+
Returns (beta_emails, alpha_only, orphaned_emails) where orphaned
|
|
51
|
+
means present in alpha or admin but missing from allowed_emails.
|
|
52
|
+
"""
|
|
53
|
+
orphaned = (alpha_emails | admin_emails) - allowed_emails
|
|
54
|
+
alpha_only = alpha_emails - admin_emails - orphaned
|
|
55
|
+
beta_emails = allowed_emails - alpha_emails - admin_emails
|
|
56
|
+
return beta_emails, alpha_only, orphaned
|
|
57
|
+
|
|
58
|
+
|
|
43
59
|
@users_app.command("list")
|
|
44
60
|
def list_users(
|
|
45
61
|
env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
|
|
62
|
+
detailed: bool = typer.Option(False, "-d", "--detailed", help="Show raw SSM parameter lists."),
|
|
46
63
|
):
|
|
47
64
|
"""Show all authorized users and their tiers."""
|
|
48
65
|
ssm = _ssm_client()
|
|
@@ -53,7 +70,7 @@ def list_users(
|
|
|
53
70
|
allowed_domains = _parse_csv(_get_param(ssm, SSM_PARAMS["allowed_domains"].format(env=env)))
|
|
54
71
|
allowed_emails = _parse_csv(_get_param(ssm, SSM_PARAMS["allowed_emails"].format(env=env)))
|
|
55
72
|
|
|
56
|
-
typer.
|
|
73
|
+
env_colored = typer.style(env, fg=typer.colors.BRIGHT_GREEN, bold=True)
|
|
57
74
|
|
|
58
75
|
def _print_section(label: str, items: set[str]) -> None:
|
|
59
76
|
typer.echo(f" {label}:")
|
|
@@ -62,13 +79,36 @@ def list_users(
|
|
|
62
79
|
else:
|
|
63
80
|
for item in sorted(items):
|
|
64
81
|
typer.echo(f" {item}")
|
|
82
|
+
typer.echo()
|
|
83
|
+
|
|
84
|
+
if detailed:
|
|
85
|
+
typer.echo(f"\n Horizyn users ({env_colored}) — detailed")
|
|
86
|
+
typer.echo()
|
|
87
|
+
typer.echo(" Access requires allowed_emails or allowed_domains match.")
|
|
88
|
+
typer.echo(" Allowed users default to beta tier unless listed in alpha or admin.")
|
|
89
|
+
typer.echo()
|
|
90
|
+
_print_section("Admin domains", admin_domains)
|
|
91
|
+
_print_section("Admin emails", admin_emails)
|
|
92
|
+
_print_section("Alpha emails", alpha_emails)
|
|
93
|
+
_print_section("Allowed domains", allowed_domains)
|
|
94
|
+
_print_section("Allowed emails", allowed_emails)
|
|
95
|
+
else:
|
|
96
|
+
beta, alpha_only, orphaned = compute_user_tiers(
|
|
97
|
+
allowed_emails, alpha_emails, admin_emails,
|
|
98
|
+
)
|
|
65
99
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
typer.echo(f"\n Horizyn users ({env_colored})")
|
|
101
|
+
typer.echo()
|
|
102
|
+
_print_section("Alpha", alpha_only)
|
|
103
|
+
_print_section("Beta", beta)
|
|
104
|
+
|
|
105
|
+
if orphaned:
|
|
106
|
+
for email in sorted(orphaned):
|
|
107
|
+
typer.secho(
|
|
108
|
+
f" ⚠ {email} is in alpha/admin but NOT in allowed_emails",
|
|
109
|
+
fg=typer.colors.RED,
|
|
110
|
+
)
|
|
111
|
+
typer.echo()
|
|
72
112
|
|
|
73
113
|
|
|
74
114
|
@users_app.command("add")
|
|
@@ -169,11 +209,12 @@ def find_user(
|
|
|
169
209
|
if email in values:
|
|
170
210
|
matches.append(param_key)
|
|
171
211
|
|
|
212
|
+
env_colored = typer.style(env, fg=typer.colors.BRIGHT_GREEN, bold=True)
|
|
172
213
|
if matches:
|
|
173
|
-
typer.echo(f" {
|
|
214
|
+
typer.echo(f" {env_colored}: found")
|
|
174
215
|
for m in matches:
|
|
175
216
|
typer.echo(f" - {m}")
|
|
176
217
|
else:
|
|
177
|
-
typer.echo(f" {
|
|
218
|
+
typer.echo(f" {env_colored}: not found")
|
|
178
219
|
|
|
179
220
|
typer.echo()
|
|
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
|
|
|
5
5
|
import pytest
|
|
6
6
|
from click.exceptions import Exit
|
|
7
7
|
|
|
8
|
-
from dh_cli.hz.users import _format_csv, _parse_csv
|
|
8
|
+
from dh_cli.hz.users import _format_csv, _parse_csv, compute_user_tiers
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class TestParseCSV:
|
|
@@ -206,3 +206,50 @@ class TestPromoteUser:
|
|
|
206
206
|
from dh_cli.hz.users import promote_user
|
|
207
207
|
|
|
208
208
|
promote_user("alice@x.com", tier="superadmin", env="prod")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestComputeUserTiers:
|
|
212
|
+
def test_beta_is_allowed_minus_alpha_minus_admin(self):
|
|
213
|
+
beta, alpha_only, orphaned = compute_user_tiers(
|
|
214
|
+
allowed_emails={"a@x.com", "b@x.com", "c@x.com"},
|
|
215
|
+
alpha_emails={"b@x.com"},
|
|
216
|
+
admin_emails=set(),
|
|
217
|
+
)
|
|
218
|
+
assert beta == {"a@x.com", "c@x.com"}
|
|
219
|
+
assert alpha_only == {"b@x.com"}
|
|
220
|
+
assert orphaned == set()
|
|
221
|
+
|
|
222
|
+
def test_admin_emails_excluded_from_alpha_and_beta(self):
|
|
223
|
+
beta, alpha_only, orphaned = compute_user_tiers(
|
|
224
|
+
allowed_emails={"a@x.com", "b@x.com", "c@x.com"},
|
|
225
|
+
alpha_emails={"b@x.com", "c@x.com"},
|
|
226
|
+
admin_emails={"c@x.com"},
|
|
227
|
+
)
|
|
228
|
+
assert beta == {"a@x.com"}
|
|
229
|
+
assert alpha_only == {"b@x.com"}
|
|
230
|
+
assert orphaned == set()
|
|
231
|
+
|
|
232
|
+
def test_orphaned_alpha_not_in_allowed(self):
|
|
233
|
+
beta, alpha_only, orphaned = compute_user_tiers(
|
|
234
|
+
allowed_emails={"a@x.com"},
|
|
235
|
+
alpha_emails={"ghost@x.com"},
|
|
236
|
+
admin_emails=set(),
|
|
237
|
+
)
|
|
238
|
+
assert beta == {"a@x.com"}
|
|
239
|
+
assert alpha_only == set()
|
|
240
|
+
assert orphaned == {"ghost@x.com"}
|
|
241
|
+
|
|
242
|
+
def test_orphaned_admin_not_in_allowed(self):
|
|
243
|
+
beta, alpha_only, orphaned = compute_user_tiers(
|
|
244
|
+
allowed_emails={"a@x.com"},
|
|
245
|
+
alpha_emails=set(),
|
|
246
|
+
admin_emails={"ghost@x.com"},
|
|
247
|
+
)
|
|
248
|
+
assert beta == {"a@x.com"}
|
|
249
|
+
assert orphaned == {"ghost@x.com"}
|
|
250
|
+
|
|
251
|
+
def test_all_empty(self):
|
|
252
|
+
beta, alpha_only, orphaned = compute_user_tiers(set(), set(), set())
|
|
253
|
+
assert beta == set()
|
|
254
|
+
assert alpha_only == set()
|
|
255
|
+
assert orphaned == set()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|