kalavai-client 0.5.30__py3-none-any.whl → 0.6.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.
@@ -1,2 +1,2 @@
1
1
 
2
- __version__ = "0.5.30"
2
+ __version__ = "0.6.0"
@@ -28,7 +28,7 @@ releases:
28
28
  - name: lago
29
29
  namespace: kalavai
30
30
  chart: kalavai/lago
31
- installed: true
31
+ installed: {{deploy_llm_sidecars}}
32
32
  set:
33
33
  - name: external.api.nodePort
34
34
  value: 32000
@@ -79,7 +79,7 @@ releases:
79
79
  namespace: kalavai
80
80
  chart: kalavai/kalavai-helios
81
81
  version: "0.1.11"
82
- installed: {{not watcher_allow_unregistered_user}}
82
+ installed: false #{{deploy_llm_sidecars}}
83
83
  set:
84
84
  - name: deployment.watcher_endpoint
85
85
  value: "http://{{watcher_service}}"
@@ -94,11 +94,11 @@ releases:
94
94
  - name: opencost
95
95
  namespace: opencost
96
96
  chart: opencost-charts/opencost
97
- installed: {{not watcher_allow_unregistered_user}}
97
+ installed: {{deploy_llm_sidecars}}
98
98
  - name: prometheus
99
99
  namespace: prometheus-system
100
100
  chart: prometheus/prometheus
101
- installed: {{not watcher_allow_unregistered_user}}
101
+ installed: {{deploy_llm_sidecars}}
102
102
  set:
103
103
  - name: prometheus-pushgateway.enabled
104
104
  value: false
@@ -144,17 +144,19 @@ releases:
144
144
  - name: kalavai-watcher
145
145
  namespace: kalavai
146
146
  chart: kalavai/kalavai-watcher
147
- version: "0.3.6"
147
+ version: "0.3.8"
148
148
  installed: true
149
149
  set:
150
150
  - name: namespace
151
151
  value: kalavai
152
152
  - name: replicas
153
- value: 2
153
+ value: 1
154
154
  - name: image_tag
155
- value: "v2025.03.12"
155
+ value: "v2025.03.18"
156
156
  - name: deployment.in_cluster
157
157
  value: "True"
158
+ - name: deployment.kalavai_username_key
159
+ value: "{{kalavai_username_key}}"
158
160
  - name: deployment.use_auth_key
159
161
  value: "True"
160
162
  - name: deployment.admin_key
@@ -1,5 +1,3 @@
1
- # TODO: add helm versions here
2
-
3
1
  - name: kalavai_api_endpoint
4
2
  default: https://platform.kalavai.net/_/api
5
3
  description: ""
@@ -10,6 +10,9 @@ services:
10
10
  environment:
11
11
  - KALAVAI_BRIDGE_URL=http://host.docker.internal
12
12
  - KALAVAI_BRIDGE_PORT={{bridge_port}}
13
+ {% if protected_access %}
14
+ - ACCESS_KEY={{protected_access}}
15
+ {% endif %}
13
16
  entrypoint: ["reflex"]
14
17
  command: >
15
18
  run
@@ -1,7 +1,7 @@
1
1
  services:
2
2
  {% if vpn %}
3
3
  {{vpn_name}}:
4
- image: gravitl/netclient:v0.24.3
4
+ image: gravitl/netclient:v0.90.0
5
5
  container_name: {{vpn_name}}
6
6
  #privileged: true
7
7
  cap_add:
kalavai_client/auth.py CHANGED
@@ -1,68 +1,110 @@
1
+ from collections import defaultdict
1
2
  import os
2
- import pickle
3
+ import json
4
+ from typing import Optional, Any
3
5
 
4
- import anvil.server
5
- import anvil.users
6
+ _USER_KEY = "id"
6
7
 
7
8
 
8
- AUTH_UPLINK_KEY = os.getenv("ANVIL_UPLINK_KEY", "client_AOPKTWK227ZV3R4ENTMOQRIY-ADMVMYW5OIRPH75P")
9
+ class KalavaiAuth():
10
+ """Client for authenticating with the Kalavai watcher service."""
11
+
12
+ def __init__(
13
+ self,
14
+ auth_service_url: str,
15
+ auth_service_key: str,
16
+ user_cookie_file: str
17
+ ):
18
+ """Initialize the Kalavai auth client.
19
+
20
+ Args:
21
+ user_cookie_file: Path to store user session data
22
+ """
23
+ self.user_cookie = user_cookie_file
24
+ self.auth_service_url = auth_service_url
25
+ self.auth_service_key = auth_service_key
26
+ self.user_session = defaultdict(None)
27
+ self.load_user_session()
9
28
 
10
- class KalavaiAuthClient:
11
- def __init__(self, user_cookie_file=None):
12
- anvil.server.connect(AUTH_UPLINK_KEY, quiet=True)
13
- self.user_cookie_file = user_cookie_file
14
- user = self.load_user_session()
29
+ def save_auth(self, user_key: str) -> Optional[Any]:
30
+ """Login with user key.
31
+
32
+ Args:
33
+ user_key: User's key (actual authentication key)
34
+
35
+ Returns:
36
+ User data if successful, None otherwise
37
+ """
38
+ # Store the user key as the session
39
+ self.user_session[_USER_KEY] = user_key
40
+ with open(self.user_cookie, "w") as f:
41
+ json.dump(self.user_session, f)
42
+ print(f"User key securely saved to {self.user_cookie}")
15
43
 
16
- def login(self, username, password):
44
+ def clear_auth(self) -> bool:
45
+ """Clear the current user and local session.
46
+
47
+ Returns:
48
+ bool: True if successful, False otherwise
49
+ """
50
+ self.user_session = defaultdict(None)
17
51
  try:
18
- user = anvil.users.login_with_email(username, password, remember=True)
19
- except:
20
- return None
52
+ if os.path.exists(self.user_cookie):
53
+ os.remove(self.user_cookie)
54
+ except Exception as e:
55
+ print(f"Failed to clear user session: {str(e)}")
56
+
57
+ def is_authenticated(self) -> bool:
58
+ """Check if user is currently authenticated.
21
59
 
22
- if self.user_cookie_file:
23
- with open(self.user_cookie_file, "wb") as f:
24
- pickle.dump(user, f)
25
- return user
60
+ Returns:
61
+ bool: True if logged in, False otherwise
62
+ """
63
+ return self.user_session[_USER_KEY] is not None
26
64
 
27
- def logout(self):
28
- anvil.users.logout()
65
+ def load_user_session(self) -> Optional[Any]:
66
+ """Load user session from local storage.
67
+
68
+ Returns:
69
+ User data if session exists, None otherwise
70
+ """
29
71
  try:
30
- os.remove(self.user_cookie_file)
31
- except:
32
- pass
72
+ if not os.path.exists(self.user_cookie):
73
+ return None
74
+
75
+ with open(self.user_cookie, "r") as f:
76
+ for key, value in json.load(f).items():
77
+ self.user_session[key] = value
78
+ return self.user_session
79
+
80
+ except Exception as e:
81
+ print(f"Failed to load user session: {str(e)}")
82
+ self.clear_auth()
83
+ return None
33
84
 
34
- def is_logged_in(self):
35
- user = self.load_user_session()
36
- return user is not None
37
-
38
- def load_user_session(self):
39
- user = anvil.users.get_user()
40
- if user:
41
- return user
85
+ def get_user_id(self) -> Optional[Any]:
86
+ """Get the user key from the user session.
87
+
88
+ Returns:
89
+ User key if session exists, None otherwise
90
+ """
42
91
  try:
43
- with open(self.user_cookie_file, "rb") as f:
44
- user = pickle.load(f)
45
- return user
92
+ return self.user_session[_USER_KEY]
46
93
  except:
47
94
  return None
48
-
49
- def call_function(self, fn, *args):
50
- return anvil.server.call(
51
- fn,
52
- *args
53
- )
54
95
 
55
-
56
96
 
97
+ # Example usage
57
98
  if __name__ == "__main__":
58
- auth = KalavaiAuthClient(
59
- user_cookie_file="here.pickle"
99
+ # Initialize client
100
+ auth = KalavaiAuth(
101
+ auth_service_url="http://localhost:8000",
102
+ auth_service_key="example_auth_service_key",
103
+ user_cookie_file="user_cookie.json"
60
104
  )
61
- user = auth.load_user_session()
62
- if not user:
63
- user = auth.login(username="carlos@kalavai.net", password="wrong_pass")
64
-
65
- if user is None:
66
- print("Failed to login")
67
- else:
68
- print(user)
105
+
106
+ # Example of logging in with a user key
107
+ user_id = "example_user_id"
108
+ auth.save_auth(user_id)
109
+
110
+ print(f"User id: {auth.get_user_id()}")
@@ -2,7 +2,8 @@
2
2
  Core kalavai service.
3
3
  Used as a bridge between the kalavai-client app and the reflex frontend
4
4
  """
5
- from fastapi import FastAPI
5
+ from fastapi import FastAPI, HTTPException, Depends
6
+ from starlette.requests import Request
6
7
  import uvicorn
7
8
 
8
9
  from kalavai_client.bridge_models import (
@@ -47,11 +48,24 @@ from kalavai_client.core import (
47
48
  uncordon_nodes,
48
49
  TokenType
49
50
  )
51
+ from kalavai_client.utils import load_user_id
50
52
 
51
53
  app = FastAPI()
52
54
 
55
+ ################################
56
+ ## API Key Validation methods ##
57
+ ################################
58
+ async def verify_api_key(request: Request):
59
+ user_id = load_user_id()
60
+ if user_id is None:
61
+ return None
62
+ api_key = request.headers.get("X-API-KEY")
63
+ if api_key != user_id:
64
+ raise HTTPException(status_code=401, detail="Request requires API Key")
65
+ return api_key
66
+
53
67
  @app.post("/create_pool")
54
- def pool_create(request: CreatePoolRequest):
68
+ def pool_create(request: CreatePoolRequest, api_key: str = Depends(verify_api_key)):
55
69
  result = create_pool(
56
70
  cluster_name=request.cluster_name,
57
71
  ip_address=request.ip_address,
@@ -66,7 +80,7 @@ def pool_create(request: CreatePoolRequest):
66
80
  return result
67
81
 
68
82
  @app.post("/join_pool")
69
- def pool_join(request: JoinPoolRequest):
83
+ def pool_join(request: JoinPoolRequest, api_key: str = Depends(verify_api_key)):
70
84
  result = join_pool(
71
85
  token=request.token,
72
86
  num_gpus=request.num_gpus,
@@ -76,7 +90,7 @@ def pool_join(request: JoinPoolRequest):
76
90
  return result
77
91
 
78
92
  @app.post("/attach_to_pool")
79
- def pool_attach(request: JoinPoolRequest):
93
+ def pool_attach(request: JoinPoolRequest, api_key: str = Depends(verify_api_key)):
80
94
  result = attach_to_pool(
81
95
  token=request.token,
82
96
  node_name=request.node_name
@@ -84,64 +98,64 @@ def pool_attach(request: JoinPoolRequest):
84
98
  return result
85
99
 
86
100
  @app.post("/stop_pool")
87
- def pool_stop(request: StopPoolRequest):
101
+ def pool_stop(request: StopPoolRequest, api_key: str = Depends(verify_api_key)):
88
102
  result = stop_pool(
89
103
  skip_node_deletion=request.skip_node_deletion
90
104
  )
91
105
  return result
92
106
 
93
107
  @app.post("/delete_nodes")
94
- def device_delete(request: NodesActionRequest):
108
+ def device_delete(request: NodesActionRequest, api_key: str = Depends(verify_api_key)):
95
109
  result = delete_nodes(
96
110
  nodes=request.nodes
97
111
  )
98
112
  return result
99
113
 
100
114
  @app.post("/cordon_nodes")
101
- def device_cordon(request: NodesActionRequest):
115
+ def device_cordon(request: NodesActionRequest, api_key: str = Depends(verify_api_key)):
102
116
  result = cordon_nodes(
103
117
  nodes=request.nodes
104
118
  )
105
119
  return result
106
120
 
107
121
  @app.post("/uncordon_nodes")
108
- def device_uncordon(request: NodesActionRequest):
122
+ def device_uncordon(request: NodesActionRequest, api_key: str = Depends(verify_api_key)):
109
123
  result = uncordon_nodes(
110
124
  nodes=request.nodes
111
125
  )
112
126
  return result
113
127
 
114
128
  @app.get("/get_pool_token")
115
- def get_token(mode: int):
129
+ def get_token(mode: int, api_key: str = Depends(verify_api_key)):
116
130
 
117
131
  return get_pool_token(mode=TokenType(mode))
118
132
 
119
133
  @app.get("/fetch_devices")
120
- def get_devices():
134
+ def get_devices(api_key: str = Depends(verify_api_key)):
121
135
  return fetch_devices()
122
136
 
123
137
  @app.post("/send_pool_invites")
124
- def send_pool_invites(request: InvitesRequest):
138
+ def send_pool_invites(request: InvitesRequest, api_key: str = Depends(verify_api_key)):
125
139
  return send_invites(invitees=request.invitees)
126
140
 
127
141
  @app.get("/fetch_resources")
128
- def resources():
142
+ def resources(api_key: str = Depends(verify_api_key)):
129
143
  return fetch_resources()
130
144
 
131
145
  @app.get("/fetch_job_names")
132
- def job_names():
146
+ def job_names(api_key: str = Depends(verify_api_key)):
133
147
  return fetch_job_names()
134
148
 
135
149
  @app.get("/fetch_gpus")
136
- def gpus(available: bool = False):
150
+ def gpus(available: bool = False, api_key: str = Depends(verify_api_key)):
137
151
  return fetch_gpus(available=available)
138
152
 
139
153
  @app.post("/fetch_job_details")
140
- def job_details(request: JobDetailsRequest):
154
+ def job_details(request: JobDetailsRequest, api_key: str = Depends(verify_api_key)):
141
155
  return fetch_job_details(jobs=request.jobs)
142
156
 
143
157
  @app.get("/fetch_job_logs")
144
- def job_logs(job_name: str, force_namespace: str=None, pod_name: str=None, tail: int=100):
158
+ def job_logs(job_name: str, force_namespace: str=None, pod_name: str=None, tail: int=100, api_key: str = Depends(verify_api_key)):
145
159
  return fetch_job_logs(
146
160
  job_name=job_name,
147
161
  force_namespace=force_namespace,
@@ -150,15 +164,15 @@ def job_logs(job_name: str, force_namespace: str=None, pod_name: str=None, tail:
150
164
  )
151
165
 
152
166
  @app.get("/fetch_job_templates")
153
- def job_templates():
167
+ def job_templates(api_key: str = Depends(verify_api_key)):
154
168
  return fetch_job_templates()
155
169
 
156
170
  @app.get("/fetch_job_defaults")
157
- def job_templates(name: str):
171
+ def job_templates(name: str, api_key: str = Depends(verify_api_key)):
158
172
  return fetch_job_defaults(name=name)
159
173
 
160
174
  @app.post("/deploy_job")
161
- def job_deploy(request: DeployJobRequest):
175
+ def job_deploy(request: DeployJobRequest, api_key: str = Depends(verify_api_key)):
162
176
  result = deploy_job(
163
177
  template_name=request.template_name,
164
178
  values_dict=request.values,
@@ -168,7 +182,7 @@ def job_deploy(request: DeployJobRequest):
168
182
  return result
169
183
 
170
184
  @app.post("/delete_job")
171
- def job_delete(request: DeleteJobRequest):
185
+ def job_delete(request: DeleteJobRequest, api_key: str = Depends(verify_api_key)):
172
186
  result = delete_job(
173
187
  name=request.name,
174
188
  force_namespace=request.force_namespace
@@ -176,15 +190,14 @@ def job_delete(request: DeleteJobRequest):
176
190
  return result
177
191
 
178
192
  @app.get("/authenticate_user")
179
- def user_authenticate(username: str, password: str):
193
+ def user_authenticate(user_id: str, api_key: str = Depends(verify_api_key)):
180
194
  result = authenticate_user(
181
- username=username,
182
- password=password
195
+ user_id=user_id
183
196
  )
184
197
  return result
185
198
 
186
199
  @app.get("/load_user_session")
187
- def user_session():
200
+ def user_session(api_key: str = Depends(verify_api_key)):
188
201
  result = load_user_session()
189
202
  return result
190
203
 
@@ -219,12 +232,12 @@ def agent_resume():
219
232
  return result
220
233
 
221
234
  @app.get("/get_ip_addresses")
222
- def ip_addresses(subnet: str=None):
235
+ def ip_addresses(subnet: str=None, api_key: str = Depends(verify_api_key)):
223
236
  result = get_ip_addresses(subnet=subnet)
224
237
  return result
225
238
 
226
239
  @app.get("/list_available_pools")
227
- def pool_connected(user_only: bool=False):
240
+ def pool_connected(user_only: bool=False, api_key: str = Depends(verify_api_key)):
228
241
  result = list_available_pools(user_only=user_only)
229
242
  return result
230
243