mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
feat(cli): add top-level deploy alias and fix KMS create_secret
- Add `hanzo deploy` as top-level alias for `hanzo paas deploy` - Fix KMS create_secret sending null secretComment (caused 422 errors) - Login callback page styling (from previous session) Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -13,7 +13,6 @@ import json
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
@@ -21,7 +20,7 @@ from typing import Any
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import click
|
||||
from hanzo_iam import IAMClient, IAMConfig, Organization
|
||||
from hanzo_iam import IAMClient, IAMConfig
|
||||
|
||||
TOKEN_DIR = Path.home() / ".hanzo" / "auth"
|
||||
TOKEN_FILE = TOKEN_DIR / "token.json"
|
||||
@@ -70,11 +69,7 @@ def _iam_app() -> str:
|
||||
|
||||
|
||||
def _iam_client_id() -> str:
|
||||
return (
|
||||
os.getenv("IAM_CLIENT_ID")
|
||||
or os.getenv("HANZO_IAM_CLIENT_ID")
|
||||
or DEFAULT_CLIENT_ID
|
||||
)
|
||||
return os.getenv("IAM_CLIENT_ID") or os.getenv("HANZO_IAM_CLIENT_ID") or DEFAULT_CLIENT_ID
|
||||
|
||||
|
||||
def get_client(ctx: click.Context | None = None) -> IAMClient:
|
||||
@@ -84,16 +79,8 @@ def get_client(ctx: click.Context | None = None) -> IAMClient:
|
||||
2. Stored bearer token from `hanzo login`
|
||||
3. Exit with instructions
|
||||
"""
|
||||
client_id = (
|
||||
os.getenv("IAM_CLIENT_ID")
|
||||
or os.getenv("HANZO_IAM_CLIENT_ID")
|
||||
or ""
|
||||
)
|
||||
client_secret = (
|
||||
os.getenv("IAM_CLIENT_SECRET")
|
||||
or os.getenv("HANZO_IAM_CLIENT_SECRET")
|
||||
or ""
|
||||
)
|
||||
client_id = os.getenv("IAM_CLIENT_ID") or os.getenv("HANZO_IAM_CLIENT_ID") or ""
|
||||
client_secret = os.getenv("IAM_CLIENT_SECRET") or os.getenv("HANZO_IAM_CLIENT_SECRET") or ""
|
||||
|
||||
if client_id and client_secret:
|
||||
config = IAMConfig(
|
||||
@@ -156,21 +143,57 @@ class _OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
if "error" in qs:
|
||||
_OAuthCallbackHandler.error = qs["error"][0]
|
||||
self._respond("Login failed. You can close this window.")
|
||||
self._respond("Login failed. You can close this window.", success=False)
|
||||
return
|
||||
|
||||
_OAuthCallbackHandler.code = qs.get("code", [None])[0]
|
||||
_OAuthCallbackHandler.state = qs.get("state", [None])[0]
|
||||
self._respond("Login successful! You can close this window.")
|
||||
|
||||
def _respond(self, message: str) -> None:
|
||||
def _respond(self, message: str, success: bool = True) -> None:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html")
|
||||
self.end_headers()
|
||||
body = (
|
||||
f"<html><body><h2>{message}</h2>"
|
||||
"<p>Return to your terminal.</p></body></html>"
|
||||
)
|
||||
accent = "#6C5CE7" if success else "#e74c3c"
|
||||
body = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Hanzo</title>
|
||||
<style>
|
||||
*{{margin:0;padding:0;box-sizing:border-box}}
|
||||
body{{
|
||||
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
||||
background:#0a0a0a;color:#fafafa;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
min-height:100vh;
|
||||
}}
|
||||
.card{{
|
||||
text-align:center;max-width:420px;padding:48px 32px;
|
||||
background:#111;border:1px solid #222;border-radius:16px;
|
||||
}}
|
||||
.logo{{font-size:28px;font-weight:700;letter-spacing:-.5px;margin-bottom:32px;color:#fff}}
|
||||
.logo span{{color:{accent}}}
|
||||
.icon{{font-size:48px;margin-bottom:20px}}
|
||||
h2{{font-size:20px;font-weight:600;margin-bottom:8px;color:#fff}}
|
||||
.sub{{color:#888;font-size:14px;line-height:1.5;margin-bottom:28px}}
|
||||
.tagline{{
|
||||
font-size:13px;color:#555;border-top:1px solid #222;
|
||||
padding-top:20px;margin-top:8px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="logo"><span>▲</span> hanzo</div>
|
||||
<div class="icon">{"✅" if success else "❌"}</div>
|
||||
<h2>{message}</h2>
|
||||
<p class="sub">You can close this window and return to your terminal.</p>
|
||||
<p class="tagline">Build something you love.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
self.wfile.write(body.encode())
|
||||
|
||||
def log_message(self, format: str, *args: Any) -> None:
|
||||
@@ -228,9 +251,7 @@ def browser_login(port: int = CALLBACK_PORT) -> dict[str, Any]:
|
||||
server.server_close()
|
||||
|
||||
if _OAuthCallbackHandler.error:
|
||||
raise click.ClickException(
|
||||
f"Login failed: {_OAuthCallbackHandler.error}"
|
||||
)
|
||||
raise click.ClickException(f"Login failed: {_OAuthCallbackHandler.error}")
|
||||
|
||||
if _OAuthCallbackHandler.state != state:
|
||||
raise click.ClickException("State mismatch — possible CSRF attack.")
|
||||
@@ -281,11 +302,7 @@ def password_login(username: str | None = None, password: str | None = None) ->
|
||||
org = _iam_org()
|
||||
app = _iam_app()
|
||||
client_id = _iam_client_id()
|
||||
client_secret = (
|
||||
os.getenv("IAM_CLIENT_SECRET")
|
||||
or os.getenv("HANZO_IAM_CLIENT_SECRET")
|
||||
or ""
|
||||
)
|
||||
client_secret = os.getenv("IAM_CLIENT_SECRET") or os.getenv("HANZO_IAM_CLIENT_SECRET") or ""
|
||||
|
||||
if not username:
|
||||
username = click.prompt("Username or email")
|
||||
|
||||
@@ -118,12 +118,15 @@ def whoami() -> None:
|
||||
|
||||
from hanzo_cli.iam.commands import iam # noqa: E402
|
||||
from hanzo_cli.kms.commands import kms # noqa: E402
|
||||
from hanzo_cli.paas.commands import paas # noqa: E402
|
||||
from hanzo_cli.paas.commands import deploy, paas # noqa: E402
|
||||
|
||||
main.add_command(iam)
|
||||
main.add_command(kms)
|
||||
main.add_command(paas)
|
||||
|
||||
# Top-level aliases — `hanzo deploy` = `hanzo paas deploy`
|
||||
main.add_command(deploy)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -333,16 +333,19 @@ class KMSClient:
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
payload: dict = {
|
||||
"workspaceId": options.project_id,
|
||||
"environment": options.environment,
|
||||
"secretPath": options.path,
|
||||
"secretValue": options.secret_value,
|
||||
"type": options.type,
|
||||
}
|
||||
if options.secret_comment is not None:
|
||||
payload["secretComment"] = options.secret_comment
|
||||
|
||||
response = self.http.post(
|
||||
f"/api/v3/secrets/raw/{options.secret_name}",
|
||||
json={
|
||||
"workspaceId": options.project_id,
|
||||
"environment": options.environment,
|
||||
"secretPath": options.path,
|
||||
"secretValue": options.secret_value,
|
||||
"secretComment": options.secret_comment,
|
||||
"type": options.type,
|
||||
},
|
||||
json=payload,
|
||||
headers=self._auth_headers(),
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
Reference in New Issue
Block a user