buildai-cli 0.3.46__tar.gz → 0.3.47__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.
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/PKG-INFO +1 -1
- buildai_cli-0.3.47/cli/auth_proxy.py +99 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/db/migrate.py +13 -1
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/guard.py +12 -4
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/pyproject.toml +1 -1
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/.gitignore +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/AGENTS.md +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/CLAUDE.md +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/buildai_bootstrap.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/__init__.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/_has_core.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/auth_local.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/__init__.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/api_proxy.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/auth.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/db/__init__.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/db/common.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/db/query.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/db/schema.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/db/status.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/dev.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/commands/doctor.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/config.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/console.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/context.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/internal_api.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/main.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/nl_query/__init__.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/nl_query/dataset_tools.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/ops_init.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/output.py +0 -0
- {buildai_cli-0.3.46 → buildai_cli-0.3.47}/cli/pagination.py +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Helpers for the sanctioned local AlloyDB Auth Proxy lane."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import socket
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from infra.settings import Settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _default_proxy_binary() -> str | None:
|
|
16
|
+
"""Return the preferred AlloyDB Auth Proxy binary path if available."""
|
|
17
|
+
|
|
18
|
+
on_path = shutil.which("alloydb-auth-proxy")
|
|
19
|
+
if on_path:
|
|
20
|
+
return on_path
|
|
21
|
+
local_bin = Path.home() / ".local" / "bin" / "alloydb-auth-proxy"
|
|
22
|
+
if local_bin.is_file():
|
|
23
|
+
return str(local_bin)
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def proxy_is_listening(*, host: str, port: int) -> bool:
|
|
28
|
+
"""Return True when a TCP listener is already present on the proxy endpoint."""
|
|
29
|
+
|
|
30
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
31
|
+
sock.settimeout(0.25)
|
|
32
|
+
return sock.connect_ex((host, port)) == 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ensure_alloydb_auth_proxy(
|
|
36
|
+
*,
|
|
37
|
+
settings: Settings,
|
|
38
|
+
target_service_account: str,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Start the sanctioned AlloyDB Auth Proxy if it is not already running.
|
|
41
|
+
|
|
42
|
+
The engineer DB lane should use one explicit transport path across CLI,
|
|
43
|
+
`psql`, and desktop tools. If the proxy is already listening, leave it
|
|
44
|
+
alone. Otherwise start it in the background and wait briefly for the port
|
|
45
|
+
to come up.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
if not settings.effective_use_alloydb_auth_proxy:
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
host = settings.alloydb_auth_proxy_host
|
|
52
|
+
port = settings.effective_alloydb_auth_proxy_port
|
|
53
|
+
if proxy_is_listening(host=host, port=port):
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
proxy_binary = _default_proxy_binary()
|
|
57
|
+
if proxy_binary is None:
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
"alloydb-auth-proxy is not installed. Re-run ./scripts/setup.sh to provision "
|
|
60
|
+
"the canonical local DB transport."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not settings.alloydb_instance_uri:
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
"AlloyDB instance URI is not configured, so the local Auth Proxy cannot start."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
command = [
|
|
69
|
+
proxy_binary,
|
|
70
|
+
settings.alloydb_instance_uri,
|
|
71
|
+
"--address",
|
|
72
|
+
host,
|
|
73
|
+
"--port",
|
|
74
|
+
str(port),
|
|
75
|
+
"--auto-iam-authn",
|
|
76
|
+
f"--impersonate-service-account={target_service_account}",
|
|
77
|
+
"--disable-built-in-telemetry",
|
|
78
|
+
]
|
|
79
|
+
if not settings.use_private_ip:
|
|
80
|
+
command.append("--public-ip")
|
|
81
|
+
|
|
82
|
+
subprocess.Popen(
|
|
83
|
+
command,
|
|
84
|
+
stdout=subprocess.DEVNULL,
|
|
85
|
+
stderr=subprocess.DEVNULL,
|
|
86
|
+
start_new_session=True,
|
|
87
|
+
env=os.environ.copy(),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
deadline = time.monotonic() + 10.0
|
|
91
|
+
while time.monotonic() < deadline:
|
|
92
|
+
if proxy_is_listening(host=host, port=port):
|
|
93
|
+
return
|
|
94
|
+
time.sleep(0.25)
|
|
95
|
+
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"Timed out waiting for alloydb-auth-proxy on {host}:{port}. "
|
|
98
|
+
"Run `uv run buildai dev db info` to inspect the expected proxy recipe."
|
|
99
|
+
)
|
|
@@ -24,6 +24,14 @@ from .common import (
|
|
|
24
24
|
set_owner_role,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
+
_MIGRATION_WRITE_PROFILES = frozenset(
|
|
28
|
+
{
|
|
29
|
+
"internal_admin",
|
|
30
|
+
"migrations-local",
|
|
31
|
+
"db-admin-local",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
27
35
|
|
|
28
36
|
def migrate(
|
|
29
37
|
ctx: typer.Context,
|
|
@@ -41,7 +49,11 @@ def migrate(
|
|
|
41
49
|
"""Run the checked-in SQL migrations on the selected DB lane."""
|
|
42
50
|
|
|
43
51
|
if target is not None and not dry_run:
|
|
44
|
-
require_write(
|
|
52
|
+
require_write(
|
|
53
|
+
ctx,
|
|
54
|
+
"Database migrations",
|
|
55
|
+
allowed_profiles=_MIGRATION_WRITE_PROFILES,
|
|
56
|
+
)
|
|
45
57
|
settings: Settings = ctx.obj["settings"]
|
|
46
58
|
|
|
47
59
|
async def run() -> None:
|
|
@@ -7,20 +7,28 @@ import typer
|
|
|
7
7
|
from cli.console import error
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def require_write(
|
|
10
|
+
def require_write(
|
|
11
|
+
ctx: typer.Context,
|
|
12
|
+
action: str,
|
|
13
|
+
*,
|
|
14
|
+
allowed_profiles: frozenset[str] | None = None,
|
|
15
|
+
) -> None:
|
|
11
16
|
"""
|
|
12
17
|
Enforce --write for mutating commands.
|
|
13
18
|
|
|
14
19
|
Args:
|
|
15
20
|
ctx: Typer context
|
|
16
21
|
action: Short description of the mutation (for error messages)
|
|
22
|
+
allowed_profiles: CLI profiles allowed to perform the mutation
|
|
17
23
|
"""
|
|
18
24
|
ctx_obj = ctx.obj or {}
|
|
19
25
|
allow_write = ctx_obj.get("allow_write", False)
|
|
20
26
|
if not allow_write:
|
|
21
27
|
error(f"{action} is a write operation. Re-run with --write.")
|
|
22
28
|
raise typer.Exit(1)
|
|
23
|
-
cli_profile = ctx_obj.get("cli_profile"
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
cli_profile = ctx_obj.get("cli_profile") or "internal_viewer"
|
|
30
|
+
required_profiles = allowed_profiles or frozenset({"internal_admin"})
|
|
31
|
+
if cli_profile not in required_profiles:
|
|
32
|
+
profile_list = ", ".join(sorted(required_profiles))
|
|
33
|
+
error(f"{action} requires one of these profiles: {profile_list}.")
|
|
26
34
|
raise typer.Exit(1)
|
|
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
|