kalavai-client 0.5.2__py3-none-any.whl → 0.5.10__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,2 @@
1
1
 
2
- __version__ = "0.5.2"
2
+ __version__ = "0.5.10"
@@ -189,13 +189,13 @@ releases:
189
189
  value: "1"
190
190
  - name: devicePlugin.deviceSplitCount
191
191
  value: "1"
192
- - name: scheduler.customWebhook.port
193
- value: "30498"
194
- - name: scheduler.service.schedulerPort
195
- value: "30498"
196
- - name: scheduler.service.monitorPort
197
- value: "30493"
198
- - name: devicePlugin.service.httpPort
199
- value: "30492"
192
+ # - name: scheduler.customWebhook.port
193
+ # value: "30498"
194
+ # - name: scheduler.service.schedulerPort
195
+ # value: "30498"
196
+ # - name: scheduler.service.monitorPort
197
+ # value: "30493"
198
+ # - name: devicePlugin.service.httpPort
199
+ # value: "30492"
200
200
 
201
201
 
@@ -1,55 +1,106 @@
1
1
  services:
2
- {{service_name}}-{{command}}:
3
- image: bundenth/kalavai-runner:gpu-latest
4
- container_name: {{service_name}}
5
- hostname: {{hostname}}
6
- privileged: true
7
- restart: unless-stopped
8
- ports:
9
- - "6443:6443" # kube server
10
- - "8472:8472" # flannel vxlan
11
- - "51820:51820" # flannel wireguard
12
- {% if command == "server" %}
13
- - "30000-30500:30000-30500"
2
+ {% if vpn %}
3
+ {{vpn_name}}:
4
+ image: gravitl/netclient:v0.30.0
5
+ container_name: {{vpn_name}}
6
+ cap_add:
7
+ - NET_ADMIN
8
+ - SYS_MODULE
9
+ network_mode: host
10
+ # networks:
11
+ # - custom-network
12
+ # ports:
13
+ # # https://docs.k3s.io/installation/requirements#inbound-rules-for-k3s-nodes
14
+ # - "6443:6443" # kube server
15
+ # - "10250:10250" # worker balancer
16
+ # - "8472:8472/udp" # flannel vxlan
17
+ # - "51820-51830:51820-51830" # flannel wireguard
18
+ # {% if command == "server" %}
19
+ # - "30000-30500:30000-30500"
20
+ # {% endif %}
21
+ environment:
22
+ - HOST_NAME={{node_name}}
23
+ - IFACE_NAME={{flannel_iface}}
24
+ - TOKEN={{vpn_token}}
25
+ volumes:
26
+ - /dev/net/tun:/dev/net/tun
27
+ restart: unless-stopped
28
+ # nginx:
29
+ # image: nginx:latest
30
+ # ports:
31
+ # - "{{redirect_source_port}}:{{redirect_source_port}}"
32
+ # restart: unless-stopped
33
+ # networks:
34
+ # - custom-network
35
+ # volumes:
36
+ # - {{nginx_path}}/nginx.conf:/etc/nginx/nginx.conf
14
37
  {% endif %}
15
- networks:
16
- - custom-network
17
- command: >
18
- {{command}}
19
- {% if command == "server" %}
20
- --flannel-backend wireguard-native
21
- --service-node-port-range "30000-30500"
22
- {% else %}
23
- --server {{pool_ip}}
24
- --token {{token}}
25
- {% endif %}
26
- --node-label role={{command}}
27
- {% if node_labels %}
28
- {{node_labels}}
29
- {% endif %}
30
- {% if num_gpus and num_gpus > 0 %}
31
- --node-label gpu=on
32
- {% else %}
33
- --node-label gpu=off
34
- {% endif %}
35
- --node-ip {{ip_address}}
36
- --node-external-ip {{ip_address}}
37
- {% if flannel_iface %}
38
- --flannel-iface {{flannel_iface}}
39
- {% endif %}
40
- volumes:
41
- - {{k3s_path}}:/var/lib/rancher/k3s # Persist data
42
- - {{etc_path}}:/etc/rancher/k3s # Config files
43
- {% if num_gpus and num_gpus > 0 %}
44
- deploy:
45
- resources:
46
- reservations:
47
- devices:
48
- - driver: nvidia
49
- count: {{num_gpus}}
50
- capabilities: [gpu]
38
+
39
+ # run worker only if command is set
40
+ {%if command %}
41
+ {{service_name}}:
42
+ image: docker.io/bundenth/kalavai-runner:gpu-latest
43
+ container_name: {{service_name}}
44
+ {% if vpn %}
45
+ depends_on:
46
+ - {{vpn_name}}
47
+ network_mode: "service:{{vpn_name}}"
48
+ {% else %}
49
+ network_mode: host
50
+ # hostname: {{node_name}}
51
+ # networks:
52
+ # - custom-network
53
+ # ports:
54
+ # - "6443:6443" # kube server
55
+ # - "2379-2380:2379-2380" # etcd server
56
+ # - "10259:10259" # kube scheduler
57
+ # - "10257:10257" # kube controller manager
58
+ # - "10250:10250" # worker balancer
59
+ # - "8285:8285" # flannel
60
+ # - "8472:8472" # flannel vxlan
61
+ # - "51820:51820" # flannel wireguard
62
+ # {% if command == "server" %}
63
+ # - "30000-32767:30000-32767"
64
+ # {% endif %}
65
+ {% endif %}
66
+ privileged: true
67
+ restart: unless-stopped
68
+ command: >
69
+ --command={{command}}
70
+ {% if command == "server" %}
71
+ --port_range="30000-32767"
72
+ {% else %}
73
+ --server_ip={{pool_ip}}
74
+ --token={{pool_token}}
75
+ {% endif %}
76
+ {%if vpn %}
77
+ --flannel_iface={{flannel_iface}}
78
+ {% endif %}
79
+ {% if num_gpus and num_gpus > 0 %}
80
+ --gpu=on
81
+ {% else %}
82
+ --gpu=off
83
+ {% endif %}
84
+ {% if node_labels %}
85
+ --extra="{{node_labels}}"
86
+ {% endif %}
87
+
88
+ volumes:
89
+ - {{k3s_path}}:/var/lib/rancher/k3s # Persist data
90
+ - {{etc_path}}:/etc/rancher/k3s # Config files
91
+
92
+ {% if num_gpus and num_gpus > 0 %}
93
+ deploy:
94
+ resources:
95
+ reservations:
96
+ devices:
97
+ - driver: nvidia
98
+ count: {{num_gpus}}
99
+ capabilities: [gpu]
100
+ {% endif %}
51
101
  {% endif %}
52
102
 
53
103
  networks:
54
104
  custom-network:
55
- driver: bridge
105
+ driver: bridge
106
+
@@ -0,0 +1,12 @@
1
+ events {}
2
+
3
+ http {
4
+ server {
5
+ listen {{redirect_source_port}};
6
+ server_name localhost;
7
+
8
+ location / {
9
+ proxy_pass http://{{redirect_container}}:{{redirect_target_port}};
10
+ }
11
+ }
12
+ }
kalavai_client/cli.py CHANGED
@@ -31,6 +31,7 @@ from kalavai_client.utils import (
31
31
  safe_remove,
32
32
  leave_vpn,
33
33
  join_vpn,
34
+ get_vpn_details,
34
35
  load_server_info,
35
36
  user_login,
36
37
  user_logout,
@@ -81,15 +82,17 @@ STORAGE_ACCESS_MODE = ["ReadWriteOnce"]
81
82
  STORAGE_CLASS_LABEL = "kalavai.storage.enabled"
82
83
  DEFAULT_STORAGE_NAME = "pool-cache"
83
84
  DEFAULT_STORAGE_SIZE = 20
85
+ DEFAULT_WATCHER_PORT = 30001
84
86
  USER_NODE_LABEL = "kalavai.cluster.user"
85
87
  KUBE_VERSION = os.getenv("KALAVAI_KUBE_VERSION", "v1.31.1+k3s1")
86
- DEFAULT_FLANNEL_IFACE = os.getenv("KALAVAI_FLANNEL_IFACE", "netmaker")
88
+ DEFAULT_FLANNEL_IFACE = os.getenv("KALAVAI_FLANNEL_IFACE", "netmaker-1")
87
89
  FORBIDEDEN_IPS = ["127.0.0.1"]
88
90
  # kalavai templates
89
91
  HELM_APPS_FILE = resource_path("kalavai_client/assets/apps.yaml")
90
92
  HELM_APPS_VALUES = resource_path("kalavai_client/assets/apps_values.yaml")
91
93
  # user specific config files
92
- DEFAULT_CONTAINER_NAME = "kalavai-seed"
94
+ DEFAULT_CONTAINER_NAME = "kalavai"
95
+ DEFAULT_VPN_CONTAINER_NAME = "kalavai-vpn"
93
96
  CONTAINER_HOST_PATH = user_path("pool/", create_path=True)
94
97
  USER_COMPOSE_FILE = user_path("docker-compose-worker.yaml")
95
98
  USER_VPN_COMPOSE_FILE = user_path("docker-compose-vpn.yaml")
@@ -115,27 +118,6 @@ CLUSTER = dockerCluster(
115
118
  ######################
116
119
  ## HELPER FUNCTIONS ##
117
120
  ######################
118
-
119
- def check_vpn_compatibility():
120
- """Check required packages to join VPN"""
121
- logs = []
122
- console.log("[white]Checking system requirements...")
123
- # netclient
124
- try:
125
- run_cmd("sudo netclient version >/dev/null 2>&1")
126
- except:
127
- logs.append("[red]Netmaker not installed. Install instructions:\n")
128
- logs.append(" Linux: https://docs.netmaker.io/docs/netclient#linux\n")
129
- logs.append(" Windows: https://docs.netmaker.io/docs/netclient#windows\n")
130
- logs.append(" MacOS: https://docs.netmaker.io/docs/netclient#mac\n")
131
-
132
- if len(logs) == 0:
133
- console.log("[green]System is ready to join a pool")
134
- return True
135
- else:
136
- for log in logs:
137
- console.log(log)
138
- return False
139
121
 
140
122
  def check_seed_compatibility():
141
123
  """Check required packages to start pools"""
@@ -179,16 +161,6 @@ def check_worker_compatibility():
179
161
 
180
162
 
181
163
  def cleanup_local():
182
- # disconnect from private network
183
- console.log("Disconnecting from VPN...")
184
- try:
185
- vpns = leave_vpn()
186
- if vpns is not None:
187
- for vpn in vpns:
188
- console.log(f"You have left {vpn} VPN")
189
- except:
190
- # no vpn
191
- pass
192
164
  console.log("Removing local cache files...")
193
165
  safe_remove(CONTAINER_HOST_PATH)
194
166
  safe_remove(USER_COMPOSE_FILE)
@@ -365,7 +337,7 @@ def select_token_type():
365
337
  break
366
338
  return {"admin": choice == 0, "user": choice == 1, "worker": choice == 2}
367
339
 
368
- def generate_compose_config(role, node_name, ip_address, node_labels, is_public, server=None, token=None):
340
+ def generate_compose_config(role, node_name, is_public, node_labels=None, pool_ip=None, vpn_token=None, pool_token=None):
369
341
  num_gpus = 0
370
342
  try:
371
343
  has_gpus = check_gpu_drivers()
@@ -377,20 +349,24 @@ def generate_compose_config(role, node_name, ip_address, node_labels, is_public,
377
349
  )
378
350
  except:
379
351
  console.log(f"[red]WARNING: error when fetching NVIDIA GPU info. GPUs will not be used on this local machine")
352
+ if node_labels is not None:
353
+ node_labels = " ".join([f"--node-label {key}={value}" for key, value in node_labels.items()])
380
354
  compose_values = {
381
355
  "user_path": user_path(""),
382
356
  "service_name": DEFAULT_CONTAINER_NAME,
383
- "pool_ip": server,
384
- "token": token,
385
- "hostname": node_name,
357
+ "vpn": is_public,
358
+ "vpn_name": DEFAULT_VPN_CONTAINER_NAME,
359
+ "pool_ip": pool_ip,
360
+ "pool_token": pool_token,
361
+ "vpn_token": vpn_token,
362
+ "node_name": node_name,
386
363
  "command": role,
387
364
  "storage_enabled": "True",
388
- "ip_address": ip_address,
389
365
  "num_gpus": num_gpus,
390
366
  "k3s_path": f"{CONTAINER_HOST_PATH}/k3s",
391
367
  "etc_path": f"{CONTAINER_HOST_PATH}/etc",
392
- "node_labels": " ".join([f"--node-label {key}={value}" for key, value in node_labels.items()]),
393
- "flannel_iface": DEFAULT_FLANNEL_IFACE if is_public else None
368
+ "node_labels": node_labels,
369
+ "flannel_iface": DEFAULT_FLANNEL_IFACE if is_public else ""
394
370
  }
395
371
  # generate local config files
396
372
  compose_yaml = load_template(
@@ -585,35 +561,56 @@ def pool__start(cluster_name, *others, only_registered_users: bool=False, ip_ad
585
561
  STORAGE_CLASS_LABEL: is_storage_compatible()
586
562
  }
587
563
  if location is not None:
588
- console.log("Joining private network")
564
+ console.log("Fetching VPN credentials")
589
565
  try:
590
- if not check_vpn_compatibility():
591
- return
592
- vpn = join_vpn(
566
+ vpn = get_vpn_details(
593
567
  location=location,
594
568
  user_cookie=USER_COOKIE)
595
569
  node_labels[USER_NODE_LABEL] = user["username"]
596
570
  except Exception as e:
597
571
  console.log(f"[red]Error when joining network: {str(e)}")
598
572
  return
599
-
600
- if ip_address is None:
601
- console.log(f"Scanning for valid IPs (subnet {vpn['subnet']})...")
602
- ip_address = select_ip_address(subnet=vpn["subnet"])
573
+
574
+ # Generate docker compose recipe
575
+ generate_compose_config(
576
+ role="server",
577
+ vpn_token=vpn["key"],
578
+ node_name=socket.gethostname(),
579
+ node_labels=node_labels,
580
+ is_public=location is not None
581
+ )
582
+
583
+ # start server
584
+ console.log("Deploying seed...")
585
+ CLUSTER.start_seed_node()
586
+
587
+ while not CLUSTER.is_agent_running():
588
+ console.log("Waiting for seed to start...")
589
+ time.sleep(10)
590
+
591
+ # select IP address (for external discovery)
592
+ if ip_address is None and location is None:
593
+ # local IP
594
+ console.log(f"Scanning for valid IPs")
595
+ ip_address = select_ip_address()
596
+ else:
597
+ # load VPN ip
598
+ ip_address = CLUSTER.get_vpn_ip()
603
599
  console.log(f"Using {ip_address} address for server")
604
600
 
601
+ # populate local cred files
605
602
  auth_key = str(uuid.uuid4())
606
603
  write_auth_key = str(uuid.uuid4())
607
604
  readonly_auth_key = str(uuid.uuid4())
608
- watcher_port = 30001
609
- watcher_service = f"{ip_address}:{watcher_port}"
605
+
606
+ watcher_service = f"{ip_address}:{DEFAULT_WATCHER_PORT}"
610
607
  values = {
611
608
  CLUSTER_NAME_KEY: cluster_name,
612
609
  CLUSTER_IP_KEY: ip_address,
613
610
  AUTH_KEY: auth_key,
614
611
  READONLY_AUTH_KEY: readonly_auth_key,
615
612
  WRITE_AUTH_KEY: write_auth_key,
616
- WATCHER_PORT_KEY: watcher_port,
613
+ WATCHER_PORT_KEY: DEFAULT_WATCHER_PORT,
617
614
  WATCHER_SERVICE_KEY: watcher_service,
618
615
  USER_NODE_LABEL_KEY: USER_NODE_LABEL,
619
616
  ALLOW_UNREGISTERED_USER_KEY: not only_registered_users
@@ -630,15 +627,6 @@ def pool__start(cluster_name, *others, only_registered_users: bool=False, ip_ad
630
627
  cluster_name=cluster_name,
631
628
  public_location=location,
632
629
  user_api_key=user["api_key"])
633
-
634
- # 1. Generate docker compose recipe
635
- compose_yaml = generate_compose_config(
636
- role="server",
637
- node_name=socket.gethostname(),
638
- ip_address=ip_address,
639
- node_labels=node_labels,
640
- is_public=location is not None
641
- )
642
630
 
643
631
  # Generate helmfile recipe
644
632
  helm_yaml = load_template(
@@ -650,14 +638,6 @@ def pool__start(cluster_name, *others, only_registered_users: bool=False, ip_ad
650
638
  f.write(helm_yaml)
651
639
 
652
640
  console.log("[green]Config files have been generated in your local machine\n")
653
-
654
- # # 1. start server
655
- console.log("Deploying seed...")
656
- CLUSTER.start_seed_node()
657
-
658
- while not CLUSTER.is_agent_running():
659
- console.log("Waiting for seed to start...")
660
- time.sleep(10)
661
641
 
662
642
  console.log("Setting pool dependencies...")
663
643
  # set template values in helmfile
@@ -691,7 +671,6 @@ def pool__start(cluster_name, *others, only_registered_users: bool=False, ip_ad
691
671
 
692
672
  return None
693
673
 
694
-
695
674
  @arguably.command
696
675
  def pool__token(*others, admin=False, user=False, worker=False):
697
676
  """
@@ -773,6 +752,7 @@ def pool__join(token, *others, node_name=None, ip_address: str=None):
773
752
  if CLUSTER.is_agent_running():
774
753
  console.log(f"[white] You are already connected to {load_server_info(data_key=CLUSTER_NAME_KEY, file=USER_LOCAL_SERVER_FILE)}. Enter [yellow]kalavai pool stop[white] to exit and join another one.")
775
754
  return
755
+
776
756
  # check that is not attached to another instance
777
757
  if os.path.exists(USER_LOCAL_SERVER_FILE):
778
758
  option = user_confirm(
@@ -810,20 +790,20 @@ def pool__join(token, *others, node_name=None, ip_address: str=None):
810
790
  }
811
791
  user = defaultdict(lambda: None)
812
792
  if public_location is not None:
813
- console.log("Joining private network")
793
+ user = user_login(user_cookie=USER_COOKIE)
794
+ if user is None:
795
+ console.log("[red]Must be logged in to join public pools. Run [yellow]kalavai login[red] to authenticate")
796
+ exit()
797
+ console.log("Fetching VPN credentials")
814
798
  try:
815
- if not check_vpn_compatibility():
816
- return
817
- vpn = join_vpn(
799
+ vpn = get_vpn_details(
818
800
  location=public_location,
819
801
  user_cookie=USER_COOKIE)
820
- user = user_login(user_cookie=USER_COOKIE)
821
802
  node_labels[USER_NODE_LABEL] = user["username"]
822
803
  except Exception as e:
823
804
  console.log(f"[red]Error when joining network: {str(e)}")
824
805
  console.log("Are you authenticated? Try [yellow]kalavai login")
825
806
  return
826
- # validate public seed
827
807
  try:
828
808
  validate_join_public_seed(
829
809
  cluster_name=cluster_name,
@@ -832,31 +812,29 @@ def pool__join(token, *others, node_name=None, ip_address: str=None):
832
812
  )
833
813
  except Exception as e:
834
814
  console.log(f"[red]Error when joining network: {str(e)}")
835
- leave_vpn(vpn_file=USER_VPN_COMPOSE_FILE)
836
815
  return
837
816
 
838
817
  # send note to server to let them know the node is coming online
839
- if not pre_join_check(node_name=node_name, server_url=watcher_service, server_key=auth_key):
840
- console.log(f"[red] Failed pre join checks. Server offline or node '{node_name}' may already exist. Please specify a different one with '--node-name'")
841
- leave_vpn(vpn_file=USER_VPN_COMPOSE_FILE)
842
- return
843
-
844
- if ip_address is None:
845
- console.log(f"Scanning for valid IPs (subnet {vpn['subnet']})...")
846
- ip_address = select_ip_address(subnet=vpn["subnet"])
847
- console.log(f"Using {ip_address} address for worker")
818
+ # TODO: won't be able to check for VPN pools...
819
+ # if not pre_join_check(node_name=node_name, server_url=watcher_service, server_key=auth_key):
820
+ # console.log(f"[red] Failed pre join checks. Server offline or node '{node_name}' may already exist. Please specify a different one with '--node-name'")
821
+ # leave_vpn(container_name=DEFAULT_VPN_CONTAINER_NAME)
822
+ # return
848
823
 
849
824
  # local agent join
850
825
  # 1. Generate local cache files
851
826
  console.log("Generating config files...")
852
- compose_yaml = generate_compose_config(
827
+
828
+ # Generate docker compose recipe
829
+ generate_compose_config(
853
830
  role="agent",
854
- server=f"https://{kalavai_seed_ip}:6443",
855
- token=kalavai_token,
856
- node_name=socket.gethostname(),
857
- ip_address=ip_address,
831
+ pool_ip=f"https://{kalavai_seed_ip}:6443",
832
+ pool_token=kalavai_token,
833
+ vpn_token=vpn["key"],
834
+ node_name=node_name,
858
835
  node_labels=node_labels,
859
836
  is_public=public_location is not None)
837
+
860
838
  store_server_info(
861
839
  server_ip=kalavai_seed_ip,
862
840
  auth_key=auth_key,
@@ -866,8 +844,6 @@ def pool__join(token, *others, node_name=None, ip_address: str=None):
866
844
  cluster_name=cluster_name,
867
845
  public_location=public_location,
868
846
  user_api_key=user["api_key"])
869
-
870
- init_user_workspace()
871
847
 
872
848
  option = user_confirm(
873
849
  question="Docker compose ready. Would you like Kalavai to deploy it?",
@@ -883,18 +859,23 @@ def pool__join(token, *others, node_name=None, ip_address: str=None):
883
859
  CLUSTER.start_worker_node()
884
860
  except Exception as e:
885
861
  console.log(f"[red] Error connecting to {cluster_name} @ {kalavai_seed_ip}. Check with the admin if the token is still valid.")
886
- leave_vpn(vpn_file=USER_VPN_COMPOSE_FILE)
862
+ leave_vpn(container_name=DEFAULT_VPN_CONTAINER_NAME)
887
863
  exit()
888
864
 
889
- while not CLUSTER.is_agent_running():
890
- console.log("Waiting for worker to start...")
891
- time.sleep(10)
865
+ # ensure we are connected
866
+ while True:
867
+ console.log("Waiting for core services to be ready, may take a few minutes...")
868
+ time.sleep(30)
869
+ if is_watcher_alive(server_creds=USER_LOCAL_SERVER_FILE, user_cookie=USER_COOKIE):
870
+ break
871
+
872
+ init_user_workspace()
892
873
 
893
874
  # set status to schedulable
894
875
  console.log(f"[green] You are connected to {cluster_name}")
895
876
 
896
877
  @arguably.command
897
- def pool__stop(*others):
878
+ def pool__stop(*others, skip_node_deletion=False):
898
879
  """
899
880
  Stop sharing your device and clean up. DO THIS ONLY IF YOU WANT TO REMOVE KALAVAI-CLIENT from your device.
900
881
 
@@ -903,7 +884,8 @@ def pool__stop(*others):
903
884
  """
904
885
  console.log("[white] Stopping kalavai app...")
905
886
  # delete local node from server
906
- node__delete(load_server_info(data_key=NODE_NAME_KEY, file=USER_LOCAL_SERVER_FILE))
887
+ if not skip_node_deletion:
888
+ node__delete(load_server_info(data_key=NODE_NAME_KEY, file=USER_LOCAL_SERVER_FILE))
907
889
  # unpublish event (only if seed node)
908
890
  # TODO: no, this should be done via the platform!!!
909
891
  # try:
@@ -916,7 +898,20 @@ def pool__stop(*others):
916
898
  # console.log(f"[red][WARNING]: (ignore if not a public pool) Error when unpublishing cluster. {str(e)}")
917
899
  # remove local node agent
918
900
  console.log("Removing agent and local cache")
901
+
902
+ # disconnect from VPN first, then remove agent, then remove local files
903
+ console.log("Disconnecting from VPN...")
904
+ try:
905
+ vpns = leave_vpn(container_name=DEFAULT_VPN_CONTAINER_NAME)
906
+ if vpns is not None:
907
+ for vpn in vpns:
908
+ console.log(f"You have left {vpn} VPN")
909
+ except:
910
+ # no vpn
911
+ pass
912
+
919
913
  CLUSTER.remove_agent()
914
+
920
915
  # clean local files
921
916
  cleanup_local()
922
917
  console.log("[white] Kalavai has stopped sharing your resources. Use [yellow]kalavai pool start[white] or [yellow]kalavai pool join[white] to start again!")
@@ -1107,6 +1102,7 @@ def pool__attach(token, *others, node_name=None):
1107
1102
  """
1108
1103
  Set creds in token on the local instance
1109
1104
  """
1105
+ # check that is not attached to another instance
1110
1106
  if os.path.exists(USER_LOCAL_SERVER_FILE):
1111
1107
  option = user_confirm(
1112
1108
  question="You seem to be connected to an instance already. Are you sure you want to join a new one?",
@@ -1115,34 +1111,39 @@ def pool__attach(token, *others, node_name=None):
1115
1111
  if option == 0:
1116
1112
  console.log("[green]Nothing happened.")
1117
1113
  return
1114
+
1115
+ # check token
1116
+ if not pool__check_token(token):
1117
+ return
1118
+
1118
1119
  try:
1119
1120
  data = decode_dict(token)
1120
1121
  kalavai_seed_ip = data[CLUSTER_IP_KEY]
1121
- kalavai_token = data[CLUSTER_TOKEN_KEY]
1122
1122
  cluster_name = data[CLUSTER_NAME_KEY]
1123
1123
  auth_key = data[AUTH_KEY]
1124
1124
  watcher_service = data[WATCHER_SERVICE_KEY]
1125
1125
  public_location = data[PUBLIC_LOCATION_KEY]
1126
- except:
1127
- console.log("[red]Error when parsing token. Invalid token")
1126
+ vpn = defaultdict(lambda: None)
1127
+ except Exception as e:
1128
+ console.log(str(e))
1129
+ console.log("[red] Invalid token")
1128
1130
  return
1129
-
1131
+
1130
1132
  user = defaultdict(lambda: None)
1131
1133
  if public_location is not None:
1132
- console.log("Joining private network")
1134
+ user = user_login(user_cookie=USER_COOKIE)
1135
+ if user is None:
1136
+ console.log("[red]Must be logged in to join public pools. Run [yellow]kalavai login[red] to authenticate")
1137
+ exit()
1138
+ console.log("Fetching VPN credentials")
1133
1139
  try:
1134
- if not check_vpn_compatibility():
1135
- return
1136
- vpn = join_vpn(
1140
+ vpn = get_vpn_details(
1137
1141
  location=public_location,
1138
1142
  user_cookie=USER_COOKIE)
1139
- user = user_login(user_cookie=USER_COOKIE)
1140
- time.sleep(5)
1141
1143
  except Exception as e:
1142
1144
  console.log(f"[red]Error when joining network: {str(e)}")
1143
1145
  console.log("Are you authenticated? Try [yellow]kalavai login")
1144
1146
  return
1145
- # validate public seed
1146
1147
  try:
1147
1148
  validate_join_public_seed(
1148
1149
  cluster_name=cluster_name,
@@ -1151,9 +1152,19 @@ def pool__attach(token, *others, node_name=None):
1151
1152
  )
1152
1153
  except Exception as e:
1153
1154
  console.log(f"[red]Error when joining network: {str(e)}")
1154
- leave_vpn(vpn_file=USER_VPN_COMPOSE_FILE)
1155
1155
  return
1156
-
1156
+
1157
+ # local agent join
1158
+ # 1. Generate local cache files
1159
+ console.log("Generating config files...")
1160
+
1161
+ # Generate docker compose recipe
1162
+ generate_compose_config(
1163
+ role="",
1164
+ vpn_token=vpn["key"],
1165
+ node_name=node_name,
1166
+ is_public=public_location is not None)
1167
+
1157
1168
  store_server_info(
1158
1169
  server_ip=kalavai_seed_ip,
1159
1170
  auth_key=auth_key,
@@ -1164,7 +1175,26 @@ def pool__attach(token, *others, node_name=None):
1164
1175
  public_location=public_location,
1165
1176
  user_api_key=user["api_key"])
1166
1177
 
1167
- console.log(f"[green]You are now connected to {cluster_name} @ {kalavai_seed_ip}")
1178
+ option = user_confirm(
1179
+ question="Docker compose ready. Would you like Kalavai to deploy it?",
1180
+ options=["no", "yes"]
1181
+ )
1182
+ if option == 0:
1183
+ console.log("Manually deploy the worker with the following command:\n")
1184
+ print(f"docker compose -f {USER_COMPOSE_FILE} up -d")
1185
+ return
1186
+
1187
+ console.log(f"[white] Connecting to {cluster_name} @ {kalavai_seed_ip} (this may take a few minutes)...")
1188
+ run_cmd(f"docker compose -f {USER_COMPOSE_FILE} up -d")
1189
+ # ensure we are connected
1190
+ while True:
1191
+ console.log("Waiting for core services to be ready, may take a few minutes...")
1192
+ time.sleep(30)
1193
+ if is_watcher_alive(server_creds=USER_LOCAL_SERVER_FILE, user_cookie=USER_COOKIE):
1194
+ break
1195
+
1196
+ # set status to schedulable
1197
+ console.log(f"[green] You are connected to {cluster_name}")
1168
1198
 
1169
1199
 
1170
1200
  @arguably.command
kalavai_client/cluster.py CHANGED
@@ -7,7 +7,8 @@ from kalavai_client.utils import (
7
7
  run_cmd,
8
8
  check_gpu_drivers,
9
9
  validate_poolconfig,
10
- user_path
10
+ user_path,
11
+ populate_template
11
12
  )
12
13
 
13
14
 
@@ -20,6 +21,9 @@ class Cluster(ABC):
20
21
  def start_worker_node(self, url, token, node_name, auth_key, watcher_service, ip_address, labels, flannel_iface):
21
22
  raise NotImplementedError()
22
23
 
24
+ @abstractmethod
25
+ def get_vpn_ip(self):
26
+ raise NotImplementedError()
23
27
 
24
28
  @abstractmethod
25
29
  def update_dependencies(self, dependencies_files):
@@ -79,11 +83,23 @@ class dockerCluster(Cluster):
79
83
  def start_seed_node(self):
80
84
 
81
85
  run_cmd(f"docker compose -f {self.compose_file} up -d")
82
- time.sleep(5)
83
- run_cmd(f"docker cp {self.container_name}:/etc/rancher/k3s/k3s.yaml {self.kubeconfig_file}")
86
+ # wait for container to be setup
87
+ while True:
88
+ try:
89
+ run_cmd(f"docker cp {self.container_name}:/etc/rancher/k3s/k3s.yaml {self.kubeconfig_file} >/dev/null 2>&1")
90
+ break
91
+ except:
92
+ pass
93
+ time.sleep(5)
84
94
 
85
95
  def start_worker_node(self):
86
96
  run_cmd(f"docker compose -f {self.compose_file} up -d")
97
+
98
+ def get_vpn_ip(self):
99
+ command = populate_template(
100
+ template_str="docker exec -it {{container_name}} ifconfig {{iface_name}} | grep 'inet ' | awk '{gsub(/^addr:/, \"\", $2); print $2}'",
101
+ values_dict={"container_name": self.container_name, "iface_name": self.default_flannel_iface})
102
+ return run_cmd(command).decode().strip()
87
103
 
88
104
 
89
105
  def update_dependencies(self, dependencies_file=None, debug=False, retries=3):
@@ -122,8 +138,13 @@ class dockerCluster(Cluster):
122
138
  def is_seed_node(self):
123
139
  if not os.path.isfile(self.compose_file):
124
140
  return False
125
- status = "server" in run_cmd(f"docker compose -f {self.compose_file} ps --services --status=running").decode()
126
- return status
141
+ if not self.is_agent_running():
142
+ return False
143
+ try:
144
+ run_cmd(f"docker container exec {self.container_name} cat /var/lib/rancher/k3s/server/node-token >/dev/null 2>&1")
145
+ return True
146
+ except:
147
+ return False
127
148
 
128
149
  def is_cluster_init(self):
129
150
  if not os.path.isfile(self.compose_file):
kalavai_client/utils.py CHANGED
@@ -2,7 +2,6 @@ import json, base64
2
2
  import os
3
3
  import requests
4
4
  from pathlib import Path
5
- from urllib.parse import urljoin
6
5
  import shutil
7
6
  import subprocess
8
7
  import re
@@ -12,10 +11,7 @@ from jinja2 import Template
12
11
 
13
12
  from rich.table import Table
14
13
  import yaml
15
- import platform
16
- import psutil
17
14
 
18
- import GPUtil
19
15
 
20
16
  from kalavai_client.auth import KalavaiAuthClient
21
17
 
@@ -245,12 +241,12 @@ def join_vpn(location, user_cookie):
245
241
  run_cmd(f"sudo netclient join -t {token} >/dev/null 2>&1")
246
242
  return vpn
247
243
 
248
- def leave_vpn():
244
+ def leave_vpn(container_name):
249
245
  try:
250
- vpns = json.loads(run_cmd("sudo netclient list").decode())
246
+ vpns = json.loads(run_cmd(f"docker exec {container_name} netclient list").decode())
251
247
  left_vpns = [vpn['network'] for vpn in vpns]
252
248
  for vpn in left_vpns:
253
- run_cmd(f"sudo netclient leave {vpn}")
249
+ run_cmd(f"docker exec {container_name} netclient leave {vpn}")
254
250
  return left_vpns
255
251
  except:
256
252
  return None
@@ -387,48 +383,13 @@ def encode_dict(data: dict):
387
383
  def decode_dict(str_data: str):
388
384
  return json.loads(base64.b64decode(str_data.encode()))
389
385
 
390
- def get_gpus():
391
- GPUs = GPUtil.getGPUs()
392
- gpus = []
393
- for gpu in GPUs:
394
- name = "nvidia" if "nvidia" in gpu.name.lower() else None
395
- if name is None:
396
- continue
397
- mem = int(gpu.memoryTotal / 1000) # in GBs
398
- gpus.append(f"{name}-{mem}GB")
399
- return ",".join(gpus)
400
-
401
- def system_uptick_request(username, node_name, backend_endpoint, backend_api_key, local_version=0):
402
- gpus = get_gpus()
403
- data = {
404
- "username": username,
405
- "system_info": {
406
- "os": platform.system(),
407
- "cpu_count": os.cpu_count(),
408
- "cpu": platform.processor(),
409
- "platform": platform.platform(),
410
- "ram": round(psutil.virtual_memory().total / (1024.0 **3)),
411
- "hostname": node_name,
412
- "gpus": gpus
413
- },
414
- "version": local_version
415
- }
416
-
417
- response = requests.post(
418
- url=urljoin(backend_endpoint, "/uptick"),
419
- json=data,
420
- headers={'X-API-KEY': backend_api_key}
421
- )
422
- response.raise_for_status()
423
- return response.json()
424
-
425
386
  def resource_path(relative_path: str):
426
387
  """ Get absolute path to resource """
427
388
  try:
428
389
  last_slash = relative_path.rfind("/")
429
390
  path = relative_path[:last_slash].replace("/", ".")
430
391
  filename = relative_path[last_slash+1:]
431
- resource = importlib.resources.path(path, filename)
392
+ resource = str(importlib.resources.files(path).joinpath(filename))
432
393
  except Exception as e:
433
394
  return None
434
395
  return resource
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kalavai-client
3
- Version: 0.5.2
3
+ Version: 0.5.10
4
4
  Summary: Client app for kalavai platform
5
5
  License: Apache-2.0
6
6
  Keywords: LLM,platform
@@ -8,9 +8,13 @@ Author: Carlos Fernandez Musoles
8
8
  Author-email: carlos@kalavai.net
9
9
  Maintainer: Carlos Fernandez Musoles
10
10
  Maintainer-email: carlos@kalavai.net
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.4
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.4
15
+ Classifier: Programming Language :: Python :: 3.5
16
+ Classifier: Programming Language :: Python :: 3.6
17
+ Classifier: Programming Language :: Python :: 3.7
14
18
  Classifier: Programming Language :: Python :: 3.8
15
19
  Classifier: Programming Language :: Python :: 3.9
16
20
  Classifier: Programming Language :: Python :: 3.10
@@ -22,7 +26,6 @@ Requires-Dist: Pillow (==10.3.0)
22
26
  Requires-Dist: anvil-uplink (==0.5.1)
23
27
  Requires-Dist: arguably (>=1.2.5)
24
28
  Requires-Dist: build ; extra == "dev"
25
- Requires-Dist: gputil (==1.4.0)
26
29
  Requires-Dist: importlib_resources (==6.5.2)
27
30
  Requires-Dist: jinja2 (==3.1.4)
28
31
  Requires-Dist: netifaces (==0.11.0)
@@ -32,7 +35,7 @@ Requires-Dist: pyinstaller (==6.5.0) ; extra == "dev"
32
35
  Requires-Dist: pyyaml (==6.0.2)
33
36
  Requires-Dist: requests (>=2.25)
34
37
  Requires-Dist: rich (==13.7.1)
35
- Requires-Dist: setuptools (<70.0.0)
38
+ Requires-Dist: setuptools (>75.0.0)
36
39
  Requires-Dist: twine ; extra == "dev"
37
40
  Project-URL: Homepage, https://platform.kalavai.net
38
41
  Project-URL: Website, https://kalavai.net
@@ -47,7 +50,7 @@ Description-Content-Type: text/markdown
47
50
 
48
51
  </div>
49
52
 
50
- ⭐⭐⭐ **Kalavai and our LLM pools are open source, and free to use in both commercial and non-commercial purposes. If you find it useful, consider supporting us by [staring our GitHub project](https://github.com/kalavai-net/kalavai-client), joining our [discord channel](https://discord.gg/HJ8FNapQ), follow our [Substack](https://kalavainet.substack.com/) and give us a [review on Product Hunt](https://www.producthunt.com/products/kalavai/reviews/new).**
53
+ ⭐⭐⭐ **Kalavai and our LLM pools are open source, and free to use in both commercial and non-commercial purposes. If you find it useful, consider supporting us by [giving a star to our GitHub project](https://github.com/kalavai-net/kalavai-client), joining our [discord channel](https://discord.gg/HJ8FNapQ), follow our [Substack](https://kalavainet.substack.com/) and give us a [review on Product Hunt](https://www.producthunt.com/products/kalavai/reviews/new).**
51
54
 
52
55
 
53
56
  # Kalavai: turn your devices into a scalable LLM platform
@@ -86,6 +89,7 @@ https://github.com/user-attachments/assets/0d2316f3-79ea-46ac-b41e-8ef720f52672
86
89
 
87
90
  ### News updates
88
91
 
92
+ - 31 January 2025: `kalavai-client` is now a [PyPI package](https://pypi.org/project/kalavai-client/), easier to install than ever!
89
93
  - 27 January 2025: Support for accessing pools from remote computers
90
94
  - 9 January 2025: Added support for [Aphrodite Engine](https://github.com/aphrodite-engine/aphrodite-engine) models
91
95
  - 8 January 2025: Release of [a free, public, shared pool](/docs/docs/public_llm_pool.md) for community LLM deployment
@@ -126,21 +130,47 @@ Not what you were looking for? [Tell us](https://github.com/kalavai-net/kalavai-
126
130
 
127
131
  ## Getting started
128
132
 
129
- The `kalavai` client is the main tool to interact with the Kalavai platform, to create and manage both local and public pools and also to interact with them (e.g. deploy models). Let's go over its installation.
133
+ The `kalavai-client` is the main tool to interact with the Kalavai platform, to create and manage both local and public pools and also to interact with them (e.g. deploy models). Let's go over its installation.
130
134
 
131
- From release **v0.5.0, you can now install `kalavai` client in non-worker computers**. You can run a pool on a set of machines and have the client on a remote computer from which you access the LLM pool. Because the client only requires having python installed, this means more computers are now supported to run it.
135
+ From release **v0.5.0, you can now install `kalavai-client` in non-worker computers**. You can run a pool on a set of machines and have the client on a remote computer from which you access the LLM pool. Because the client only requires having python installed, this means more computers are now supported to run it.
132
136
 
133
137
 
134
- ### Requirements for a worker machine
138
+ ### Requirements
139
+
140
+ For workers sharing resources with the pool:
135
141
 
136
142
  - A laptop, desktop or Virtual Machine
137
143
  - Docker engine installed (for [linux](https://docs.docker.com/engine/install/), [Windows and MacOS](https://docs.docker.com/desktop/)) with [privilege access](https://docs.docker.com/engine/containers/run/#runtime-privilege-and-linux-capabilities).
138
144
 
145
+ > **Support for Windows and MacOS workers is experimental**: kalavai workers run on docker containers that require access to the host network interfaces, thus systems that do not support containers natively (Windows and MacOS) may have difficulties finding each other.
146
+
147
+ Any system that runs python 3.6+ is able to run the `kalavai-client` and therefore connect and operate an LLM pool, [without sharing with the pool](). Your computer won't be adding its capacity to the pool, but it wil be able to deploy jobs and interact with models.
148
+
149
+
150
+ #### Common issues
151
+
152
+ If you see the following error:
139
153
 
140
- ### Requirements to run the client
154
+ ```bash
155
+ fatal error: Python.h: No such file or directory | #include <Python.h>
156
+ ```
141
157
 
142
- - Python 3.10+
158
+ Make sure you also install python3-dev package. For ubuntu distros:
143
159
 
160
+ ```bash
161
+ sudo apt install python3-dev
162
+ ```
163
+
164
+ If you see:
165
+ ```bash
166
+ AttributeError: install_layout. Did you mean: 'install_platlib'?
167
+ [end of output]
168
+ ```
169
+
170
+ Upgrade your setuptools:
171
+ ```bash
172
+ pip install -U setuptools
173
+ ```
144
174
 
145
175
  ### Install the client
146
176
 
@@ -186,6 +216,17 @@ Copy the joining token. On the worker node, run:
186
216
  kalavai pool join <token>
187
217
  ```
188
218
 
219
+ ### 3. Attach more clients
220
+
221
+ You can now connect to an existing pool from any computer -not just from worker nodes. To connect to a pool, run:
222
+
223
+ ```bash
224
+ kalavai pool attach <token>
225
+ ```
226
+
227
+ This won't add the machine as a worker, but you will be able to operate in the pool as if you were. This is ideal for remote access to the pool, and to use the pool from machines that cannot run workers (docker container limitations).
228
+
229
+
189
230
  ### Enough already, let's run stuff!
190
231
 
191
232
  Check our [examples](examples/) to put your new AI pool to good use!
@@ -250,12 +291,16 @@ Anything missing here? Give us a shout in the [discussion board](https://github.
250
291
 
251
292
  ### Requirements
252
293
 
253
- Python version <= 3.12.
294
+ Python version >= 3.6.
254
295
 
255
296
  ```bash
256
- virtualenv -p python3 env
297
+ sudo add-apt-repository ppa:deadsnakes/ppa
298
+ sudo apt update
299
+ sudo apt install python3.10 python3.10-dev python3-virtualenv
300
+ virtualenv -p python3.10 env
257
301
  source env/bin/activate
258
302
  sudo apt install python3.10-venv python3.10-dev -y
303
+ pip install -U setuptools
259
304
  pip install -e .[dev]
260
305
  ```
261
306
 
@@ -273,3 +318,4 @@ To run the unit tests, use:
273
318
  python -m unittest
274
319
  ```
275
320
 
321
+ docker run --rm --net=host -v /root/.cache/kalavai/:/root/.cache/kalavai/ ghcr.io/helmfile/helmfile:v0.169.2 helmfile sync --file /root/.cache/kalavai/apps.yaml --kubeconfig /root/.cache/kalavai/kubeconfig
@@ -0,0 +1,20 @@
1
+ kalavai_client/__init__.py,sha256=PoEyxKkxe5kSNJwy0utYwv_ANRK98k-p4OLs19lJaBA,23
2
+ kalavai_client/__main__.py,sha256=WQUfxvRsBJH5gsCJg8pLz95QnZIj7Ol8psTO77m0QE0,73
3
+ kalavai_client/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ kalavai_client/assets/apps.yaml,sha256=_8BgT9F611c8uZJvhTE_0CLbQqnLaUQosqxZjzOslXQ,5979
5
+ kalavai_client/assets/apps_values.yaml,sha256=CjKVelPQHd-hm-DTMEuya92feKiphU9mh3HrosLYYPE,1676
6
+ kalavai_client/assets/docker-compose-template.yaml,sha256=gJ0NkhcG2c-gZPmSd385dadrXkZrWruTJkiaxcaKkQ0,2725
7
+ kalavai_client/assets/nginx.conf,sha256=drVVCg8GHucz7hmt_BI6giAhK92OV71257NTs3LthwM,225
8
+ kalavai_client/assets/pool_config_template.yaml,sha256=fFz4w2-fMKD5KvyzFdfcWD_jSneRlmnjLc8hCctweX0,576
9
+ kalavai_client/assets/pool_config_values.yaml,sha256=VrM3XHQfQo6QLZ68qvagooUptaYgl1pszniY_JUtemk,233
10
+ kalavai_client/assets/user_workspace.yaml,sha256=wDvlMYknOPABAEo0dsQwU7bac8iubjAG9tdkFbJZ5Go,476
11
+ kalavai_client/assets/user_workspace_values.yaml,sha256=G0HOzQUxrDMCwuW9kbWUZaKMzDDPVwDwzBHCL2Xi2ZM,542
12
+ kalavai_client/auth.py,sha256=QsBh28L2LwjBBK6pTUE4Xu36lLDTyetyU1YfS1Hbb6g,1717
13
+ kalavai_client/cli.py,sha256=4qTZuYNFhsdJbnER-MSBJHPNgJc_lWzbWR0Bj2YeQe0,68889
14
+ kalavai_client/cluster.py,sha256=fULTAad4KXEGeWZmp4_VBoBwT5eED_HOBUsXIKmf0CU,12119
15
+ kalavai_client/utils.py,sha256=RTJNgY7ho52Q5WzV68ZK5uHNYRAwXkelIr1PmBLBJsk,12420
16
+ kalavai_client-0.5.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ kalavai_client-0.5.10.dist-info/METADATA,sha256=u_E2mWeRmmVE2pWCToOVwwxdgu--UWkzhkImQb67qy4,14101
18
+ kalavai_client-0.5.10.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
19
+ kalavai_client-0.5.10.dist-info/entry_points.txt,sha256=9T6D45gxwzfVbglMm1r6XPdXuuZdHfy_7fCeu2jUphc,50
20
+ kalavai_client-0.5.10.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- # https://docs.netmaker.io/docs/netclient#docker
2
- services:
3
- {{service_name}}:
4
- network_mode: host
5
- privileged: true
6
- restart: always
7
- environment:
8
- - TOKEN={{vpn_token}}
9
- - IFACE_NAME={{flannel_iface}}
10
- volumes:
11
- - '{{etc_path}}/netclient:/etc/netclient'
12
- container_name: {{service_name}}
13
- image: 'gravitl/netclient:latest'
@@ -1,20 +0,0 @@
1
- kalavai_client/__init__.py,sha256=Ihhobsu68mjIUrgtsfOab7vjkNbLMm1uv7WvMKrKu8c,22
2
- kalavai_client/__main__.py,sha256=WQUfxvRsBJH5gsCJg8pLz95QnZIj7Ol8psTO77m0QE0,73
3
- kalavai_client/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- kalavai_client/assets/apps.yaml,sha256=aW9wKyvZhZFMHzBkZOsgVq-kpntED6U8B9XoHkm5F9Y,5963
5
- kalavai_client/assets/apps_values.yaml,sha256=CjKVelPQHd-hm-DTMEuya92feKiphU9mh3HrosLYYPE,1676
6
- kalavai_client/assets/docker-compose-template.yaml,sha256=qDv0og338clLobDDPEJ-HiGlMcCMMx2NOi5R_hdhKvw,1442
7
- kalavai_client/assets/pool_config_template.yaml,sha256=fFz4w2-fMKD5KvyzFdfcWD_jSneRlmnjLc8hCctweX0,576
8
- kalavai_client/assets/pool_config_values.yaml,sha256=VrM3XHQfQo6QLZ68qvagooUptaYgl1pszniY_JUtemk,233
9
- kalavai_client/assets/user_workspace.yaml,sha256=wDvlMYknOPABAEo0dsQwU7bac8iubjAG9tdkFbJZ5Go,476
10
- kalavai_client/assets/user_workspace_values.yaml,sha256=G0HOzQUxrDMCwuW9kbWUZaKMzDDPVwDwzBHCL2Xi2ZM,542
11
- kalavai_client/assets/vpn-template.yaml,sha256=Hm7sevtrsakSxSKMJwl68hzOEWCaxwYytwkTgKhe_MM,397
12
- kalavai_client/auth.py,sha256=QsBh28L2LwjBBK6pTUE4Xu36lLDTyetyU1YfS1Hbb6g,1717
13
- kalavai_client/cli.py,sha256=qBOe4IULIX7pzKUhHoR1JppWrbUeV1aslxQLMWrRGpU,67913
14
- kalavai_client/cluster.py,sha256=C7uofrpSEj4PUhF_VLfwH4k9BhJMTkuKRWHxDYu1OD0,11345
15
- kalavai_client/utils.py,sha256=XO-fTrl8vudAOYK9QwuR-C5enzXORIs5A4sFIk3eA9k,13476
16
- kalavai_client-0.5.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
- kalavai_client-0.5.2.dist-info/METADATA,sha256=K-7NcUKhmWlkGyYmQ9hrnbKqN7UuGHUV6lQPnqxdSLg,12077
18
- kalavai_client-0.5.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
19
- kalavai_client-0.5.2.dist-info/entry_points.txt,sha256=9T6D45gxwzfVbglMm1r6XPdXuuZdHfy_7fCeu2jUphc,50
20
- kalavai_client-0.5.2.dist-info/RECORD,,