gravi-cli 0.2.0__tar.gz → 0.2.2__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.
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/.bumpversion.toml +1 -1
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/PKG-INFO +1 -1
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/__init__.py +1 -1
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/client.py +12 -1
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/exceptions.py +5 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/tunnel.py +7 -3
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/pyproject.toml +1 -1
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/.gitignore +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/LICENSE +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/README.md +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/api.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/auth.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/cli.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/gravi_cli/config.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/release.sh +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/__init__.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/conftest.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/test_api.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/test_auth.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/test_cli.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/test_client.py +0 -0
- {gravi_cli-0.2.0 → gravi_cli-0.2.2}/tests/test_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gravi-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: CLI tool for Gravitate infrastructure management
|
|
5
5
|
Project-URL: Homepage, https://github.com/gravitate/mom
|
|
6
6
|
Project-URL: Documentation, https://github.com/gravitate/mom/tree/main/cli
|
|
@@ -7,7 +7,7 @@ Handles all HTTP communication with the mom backend API.
|
|
|
7
7
|
import requests
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from .exceptions import APIError, InvalidTokenError, RateLimitError
|
|
10
|
+
from .exceptions import APIError, InvalidTokenError, PermissionDeniedError, RateLimitError
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class MomClient:
|
|
@@ -49,6 +49,7 @@ class MomClient:
|
|
|
49
49
|
APIError: For non-2xx responses
|
|
50
50
|
RateLimitError: For 429 rate limit responses
|
|
51
51
|
InvalidTokenError: For 401 authentication failures
|
|
52
|
+
PermissionDeniedError: For 403 permission denied responses
|
|
52
53
|
"""
|
|
53
54
|
url = f"{self.mom_url}/{endpoint}"
|
|
54
55
|
|
|
@@ -88,6 +89,16 @@ class MomClient:
|
|
|
88
89
|
response_data=response.json() if response.content else None
|
|
89
90
|
)
|
|
90
91
|
|
|
92
|
+
# Handle permission errors
|
|
93
|
+
if response.status_code == 403:
|
|
94
|
+
error_data = response.json() if response.content else {}
|
|
95
|
+
detail = error_data.get("detail", "")
|
|
96
|
+
raise PermissionDeniedError(
|
|
97
|
+
f"You don't have permission to access this resource. {detail}".strip(),
|
|
98
|
+
status_code=403,
|
|
99
|
+
response_data=error_data
|
|
100
|
+
)
|
|
101
|
+
|
|
91
102
|
# Handle other errors
|
|
92
103
|
if not response.ok:
|
|
93
104
|
error_data = response.json() if response.content else {}
|
|
@@ -26,6 +26,11 @@ class InvalidTokenError(APIError):
|
|
|
26
26
|
pass
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
class PermissionDeniedError(APIError):
|
|
30
|
+
"""Raised when user doesn't have permission to access a resource."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
class RateLimitError(APIError):
|
|
30
35
|
"""Raised when rate limit is exceeded."""
|
|
31
36
|
pass
|
|
@@ -6,6 +6,7 @@ exposing them on local ports for tools like MongoDB Compass or redis-cli.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
|
+
import json
|
|
9
10
|
import socket
|
|
10
11
|
import signal
|
|
11
12
|
import sys
|
|
@@ -81,10 +82,10 @@ class TunnelClient:
|
|
|
81
82
|
self._shutdown_event = asyncio.Event()
|
|
82
83
|
|
|
83
84
|
def _get_ws_url(self, service: str) -> str:
|
|
84
|
-
"""Get WebSocket URL for a service tunnel."""
|
|
85
|
+
"""Get WebSocket URL for a service tunnel (no token in URL)."""
|
|
85
86
|
# Convert HTTP to WebSocket URL
|
|
86
87
|
ws_url = self.mom_url.replace("https://", "wss://").replace("http://", "ws://")
|
|
87
|
-
return f"{ws_url}/burners/{self.burner_id}/tunnel/{service}
|
|
88
|
+
return f"{ws_url}/burners/{self.burner_id}/tunnel/{service}"
|
|
88
89
|
|
|
89
90
|
async def _handle_client(
|
|
90
91
|
self,
|
|
@@ -94,10 +95,13 @@ class TunnelClient:
|
|
|
94
95
|
) -> None:
|
|
95
96
|
"""Handle a single client connection by relaying to WebSocket."""
|
|
96
97
|
ws_url = self._get_ws_url(service)
|
|
97
|
-
peer = writer.get_extra_info("peername")
|
|
98
98
|
|
|
99
99
|
try:
|
|
100
100
|
async with websockets.connect(ws_url) as ws:
|
|
101
|
+
# Send auth message first (token not in URL for security)
|
|
102
|
+
auth_message = json.dumps({"type": "auth", "token": self.token})
|
|
103
|
+
await ws.send(auth_message)
|
|
104
|
+
|
|
101
105
|
async def tcp_to_ws():
|
|
102
106
|
try:
|
|
103
107
|
while True:
|
|
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
|