toolgate-io 0.3.0__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.
- toolgate/__init__.py +3 -0
- toolgate/cli/__init__.py +1 -0
- toolgate/cli/client.py +49 -0
- toolgate/cli/config.py +48 -0
- toolgate/cli/main.py +717 -0
- toolgate/core/__init__.py +152 -0
- toolgate/core/assertion.py +189 -0
- toolgate/core/audit.py +119 -0
- toolgate/core/canonical.py +12 -0
- toolgate/core/errors.py +51 -0
- toolgate/core/ids.py +13 -0
- toolgate/core/keys.py +47 -0
- toolgate/core/policy.py +184 -0
- toolgate/core/token.py +130 -0
- toolgate/core/types.py +284 -0
- toolgate/demo.py +280 -0
- toolgate/sdk/__init__.py +19 -0
- toolgate/sdk/client.py +170 -0
- toolgate/server/__init__.py +15 -0
- toolgate/server/app.py +57 -0
- toolgate/server/context.py +132 -0
- toolgate/server/control.py +364 -0
- toolgate/server/gate.py +376 -0
- toolgate/server/main.py +21 -0
- toolgate/server/store.py +280 -0
- toolgate/server/vault.py +35 -0
- toolgate_io-0.3.0.dist-info/METADATA +166 -0
- toolgate_io-0.3.0.dist-info/RECORD +32 -0
- toolgate_io-0.3.0.dist-info/WHEEL +4 -0
- toolgate_io-0.3.0.dist-info/entry_points.txt +4 -0
- toolgate_io-0.3.0.dist-info/licenses/LICENSE +202 -0
- toolgate_io-0.3.0.dist-info/licenses/NOTICE +4 -0
toolgate/__init__.py
ADDED
toolgate/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Toolgate operator CLI — a pure client of the control-plane API."""
|
toolgate/cli/client.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
from .config import Profile
|
|
8
|
+
|
|
9
|
+
err_console = Console(stderr=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AdminClient:
|
|
13
|
+
"""Thin, honest wrapper over the control-plane API. Any non-2xx response is
|
|
14
|
+
rendered as the server's error envelope and exits non-zero."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, profile: Profile) -> None:
|
|
17
|
+
self.url = profile.url
|
|
18
|
+
self._http = httpx.Client(
|
|
19
|
+
base_url=profile.url,
|
|
20
|
+
headers={"x-toolgate-admin-key": profile.admin_key},
|
|
21
|
+
timeout=15.0,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def get(self, path: str, **params: Any) -> Any:
|
|
25
|
+
return self._handle(
|
|
26
|
+
self._http.get(path, params={k: v for k, v in params.items() if v is not None})
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def post(self, path: str, body: dict[str, Any] | None = None) -> Any:
|
|
30
|
+
return self._handle(self._http.post(path, json=body or {}))
|
|
31
|
+
|
|
32
|
+
def public(self, path: str) -> Any:
|
|
33
|
+
# Unauthenticated endpoints (/healthz, /v1/keys).
|
|
34
|
+
return self._handle(httpx.get(f"{self.url}{path}", timeout=15.0))
|
|
35
|
+
|
|
36
|
+
def _handle(self, res: httpx.Response) -> Any:
|
|
37
|
+
try:
|
|
38
|
+
body = res.json()
|
|
39
|
+
except ValueError:
|
|
40
|
+
body = {"error": {"code": "TG_INTERNAL", "message": res.text[:200]}}
|
|
41
|
+
if res.status_code >= 400:
|
|
42
|
+
err = body.get("error", {})
|
|
43
|
+
err_console.print(
|
|
44
|
+
f"[bold red]{err.get('code', res.status_code)}[/] {err.get('message', '')}"
|
|
45
|
+
)
|
|
46
|
+
if err.get("details"):
|
|
47
|
+
err_console.print(f"[dim]{err['details']}[/]")
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
return body
|
toolgate/cli/config.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def config_path() -> Path:
|
|
8
|
+
return Path(os.environ.get("TOOLGATE_CONFIG", "~/.toolgate/config.json")).expanduser()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Profile:
|
|
13
|
+
url: str
|
|
14
|
+
admin_key: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_profiles() -> dict[str, dict[str, str]]:
|
|
18
|
+
path = config_path()
|
|
19
|
+
if not path.exists():
|
|
20
|
+
return {}
|
|
21
|
+
return json.loads(path.read_text())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_profile(name: str, url: str, admin_key: str) -> Path:
|
|
25
|
+
path = config_path()
|
|
26
|
+
profiles = load_profiles()
|
|
27
|
+
profiles[name] = {"url": url.rstrip("/"), "admin_key": admin_key}
|
|
28
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
path.write_text(json.dumps(profiles, indent=2) + "\n")
|
|
30
|
+
path.chmod(0o600)
|
|
31
|
+
return path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def resolve(profile: str | None) -> Profile:
|
|
35
|
+
"""Environment wins (CI/scripting); otherwise the named or default profile."""
|
|
36
|
+
env_url = os.environ.get("TOOLGATE_URL")
|
|
37
|
+
env_key = os.environ.get("TOOLGATE_ADMIN_KEY")
|
|
38
|
+
if env_url and env_key:
|
|
39
|
+
return Profile(url=env_url.rstrip("/"), admin_key=env_key)
|
|
40
|
+
|
|
41
|
+
profiles = load_profiles()
|
|
42
|
+
name = profile or "default"
|
|
43
|
+
if name not in profiles:
|
|
44
|
+
raise LookupError(
|
|
45
|
+
f"no profile '{name}' — run `toolgate init` or set TOOLGATE_URL and TOOLGATE_ADMIN_KEY"
|
|
46
|
+
)
|
|
47
|
+
entry = profiles[name]
|
|
48
|
+
return Profile(url=entry["url"], admin_key=entry["admin_key"])
|