kaqing 2.0.14__py3-none-any.whl → 2.0.189__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.

Potentially problematic release.


This version of kaqing might be problematic. Click here for more details.

Files changed (228) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -12
  3. adam/apps.py +20 -6
  4. adam/batch.py +16 -6
  5. adam/checks/check_utils.py +19 -49
  6. adam/checks/compactionstats.py +1 -1
  7. adam/checks/cpu.py +9 -3
  8. adam/checks/cpu_metrics.py +52 -0
  9. adam/checks/disk.py +3 -4
  10. adam/checks/gossip.py +1 -1
  11. adam/checks/memory.py +3 -3
  12. adam/checks/status.py +1 -1
  13. adam/columns/columns.py +3 -1
  14. adam/columns/cpu.py +3 -1
  15. adam/columns/cpu_metrics.py +22 -0
  16. adam/columns/memory.py +3 -4
  17. adam/commands/__init__.py +24 -0
  18. adam/commands/alter_tables.py +66 -0
  19. adam/commands/app/app.py +38 -0
  20. adam/commands/{app_ping.py → app/app_ping.py} +8 -14
  21. adam/commands/app/show_app_actions.py +49 -0
  22. adam/commands/{show → app}/show_app_id.py +9 -12
  23. adam/commands/{show → app}/show_app_queues.py +8 -14
  24. adam/commands/app/utils_app.py +106 -0
  25. adam/commands/audit/__init__.py +0 -0
  26. adam/commands/audit/audit.py +67 -0
  27. adam/commands/audit/audit_repair_tables.py +72 -0
  28. adam/commands/audit/audit_run.py +50 -0
  29. adam/commands/audit/completions_l.py +15 -0
  30. adam/commands/audit/show_last10.py +36 -0
  31. adam/commands/audit/show_slow10.py +36 -0
  32. adam/commands/audit/show_top10.py +36 -0
  33. adam/commands/audit/utils_show_top10.py +71 -0
  34. adam/commands/bash/__init__.py +5 -0
  35. adam/commands/bash/bash.py +36 -0
  36. adam/commands/bash/bash_completer.py +93 -0
  37. adam/commands/bash/utils_bash.py +16 -0
  38. adam/commands/cat.py +36 -0
  39. adam/commands/cd.py +14 -88
  40. adam/commands/check.py +18 -21
  41. adam/commands/cli_commands.py +11 -7
  42. adam/commands/clipboard_copy.py +87 -0
  43. adam/commands/code.py +57 -0
  44. adam/commands/command.py +220 -19
  45. adam/commands/commands_utils.py +28 -31
  46. adam/commands/cql/__init__.py +0 -0
  47. adam/commands/cql/completions_c.py +28 -0
  48. adam/commands/{cqlsh.py → cql/cqlsh.py} +13 -32
  49. adam/commands/cql/utils_cql.py +305 -0
  50. adam/commands/deploy/code_start.py +7 -10
  51. adam/commands/deploy/code_stop.py +4 -21
  52. adam/commands/deploy/code_utils.py +5 -5
  53. adam/commands/deploy/deploy.py +4 -40
  54. adam/commands/deploy/deploy_frontend.py +15 -18
  55. adam/commands/deploy/deploy_pg_agent.py +4 -7
  56. adam/commands/deploy/deploy_pod.py +74 -77
  57. adam/commands/deploy/deploy_utils.py +16 -26
  58. adam/commands/deploy/undeploy.py +4 -40
  59. adam/commands/deploy/undeploy_frontend.py +5 -8
  60. adam/commands/deploy/undeploy_pg_agent.py +7 -8
  61. adam/commands/deploy/undeploy_pod.py +16 -17
  62. adam/commands/devices/__init__.py +0 -0
  63. adam/commands/devices/device.py +149 -0
  64. adam/commands/devices/device_app.py +163 -0
  65. adam/commands/devices/device_auit_log.py +49 -0
  66. adam/commands/devices/device_cass.py +179 -0
  67. adam/commands/devices/device_export.py +87 -0
  68. adam/commands/devices/device_postgres.py +160 -0
  69. adam/commands/devices/devices.py +25 -0
  70. adam/commands/download_file.py +47 -0
  71. adam/commands/exit.py +1 -4
  72. adam/commands/export/__init__.py +0 -0
  73. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  74. adam/commands/export/clean_up_export_sessions.py +39 -0
  75. adam/commands/export/completions_x.py +11 -0
  76. adam/commands/export/download_export_session.py +40 -0
  77. adam/commands/export/drop_export_database.py +39 -0
  78. adam/commands/export/drop_export_databases.py +37 -0
  79. adam/commands/export/export.py +37 -0
  80. adam/commands/export/export_databases.py +246 -0
  81. adam/commands/export/export_select.py +34 -0
  82. adam/commands/export/export_sessions.py +209 -0
  83. adam/commands/export/export_use.py +49 -0
  84. adam/commands/export/export_x_select.py +48 -0
  85. adam/commands/export/exporter.py +332 -0
  86. adam/commands/export/import_files.py +44 -0
  87. adam/commands/export/import_session.py +44 -0
  88. adam/commands/export/importer.py +81 -0
  89. adam/commands/export/importer_athena.py +148 -0
  90. adam/commands/export/importer_sqlite.py +67 -0
  91. adam/commands/export/show_column_counts.py +45 -0
  92. adam/commands/export/show_export_databases.py +39 -0
  93. adam/commands/export/show_export_session.py +39 -0
  94. adam/commands/export/show_export_sessions.py +37 -0
  95. adam/commands/export/utils_export.py +344 -0
  96. adam/commands/find_files.py +51 -0
  97. adam/commands/find_processes.py +76 -0
  98. adam/commands/head.py +36 -0
  99. adam/commands/help.py +14 -9
  100. adam/commands/intermediate_command.py +52 -0
  101. adam/commands/issues.py +14 -40
  102. adam/commands/kubectl.py +38 -0
  103. adam/commands/login.py +26 -25
  104. adam/commands/logs.py +5 -7
  105. adam/commands/ls.py +11 -115
  106. adam/commands/medusa/medusa.py +4 -46
  107. adam/commands/medusa/medusa_backup.py +22 -29
  108. adam/commands/medusa/medusa_restore.py +51 -49
  109. adam/commands/medusa/medusa_show_backupjobs.py +20 -21
  110. adam/commands/medusa/medusa_show_restorejobs.py +16 -21
  111. adam/commands/medusa/utils_medusa.py +15 -0
  112. adam/commands/nodetool.py +8 -17
  113. adam/commands/param_get.py +11 -14
  114. adam/commands/param_set.py +9 -13
  115. adam/commands/postgres/completions_p.py +22 -0
  116. adam/commands/postgres/postgres.py +49 -73
  117. adam/commands/postgres/postgres_databases.py +270 -0
  118. adam/commands/postgres/postgres_ls.py +4 -8
  119. adam/commands/postgres/postgres_preview.py +5 -9
  120. adam/commands/postgres/utils_postgres.py +79 -0
  121. adam/commands/preview_table.py +10 -69
  122. adam/commands/pwd.py +14 -43
  123. adam/commands/reaper/reaper.py +6 -49
  124. adam/commands/reaper/reaper_forward.py +49 -56
  125. adam/commands/reaper/reaper_forward_session.py +6 -0
  126. adam/commands/reaper/reaper_forward_stop.py +10 -16
  127. adam/commands/reaper/reaper_restart.py +8 -15
  128. adam/commands/reaper/reaper_run_abort.py +8 -33
  129. adam/commands/reaper/reaper_runs.py +43 -58
  130. adam/commands/reaper/reaper_runs_abort.py +29 -49
  131. adam/commands/reaper/reaper_schedule_activate.py +14 -33
  132. adam/commands/reaper/reaper_schedule_start.py +9 -33
  133. adam/commands/reaper/reaper_schedule_stop.py +9 -33
  134. adam/commands/reaper/reaper_schedules.py +4 -14
  135. adam/commands/reaper/reaper_status.py +8 -16
  136. adam/commands/reaper/utils_reaper.py +203 -0
  137. adam/commands/repair/repair.py +4 -46
  138. adam/commands/repair/repair_log.py +6 -12
  139. adam/commands/repair/repair_run.py +29 -36
  140. adam/commands/repair/repair_scan.py +33 -41
  141. adam/commands/repair/repair_stop.py +6 -13
  142. adam/commands/report.py +25 -21
  143. adam/commands/restart.py +27 -28
  144. adam/commands/rollout.py +20 -25
  145. adam/commands/shell.py +12 -4
  146. adam/commands/show/show.py +15 -46
  147. adam/commands/show/show_adam.py +3 -3
  148. adam/commands/show/show_cassandra_repairs.py +37 -0
  149. adam/commands/show/show_cassandra_status.py +48 -52
  150. adam/commands/show/show_cassandra_version.py +5 -18
  151. adam/commands/show/show_cli_commands.py +56 -0
  152. adam/commands/show/show_host.py +33 -0
  153. adam/commands/show/show_login.py +23 -27
  154. adam/commands/show/show_params.py +2 -5
  155. adam/commands/show/show_processes.py +18 -21
  156. adam/commands/show/show_storage.py +11 -20
  157. adam/commands/watch.py +27 -30
  158. adam/config.py +8 -6
  159. adam/embedded_params.py +1 -1
  160. adam/log.py +4 -4
  161. adam/pod_exec_result.py +13 -5
  162. adam/repl.py +136 -120
  163. adam/repl_commands.py +66 -24
  164. adam/repl_session.py +8 -1
  165. adam/repl_state.py +343 -73
  166. adam/sql/__init__.py +0 -0
  167. adam/sql/lark_completer.py +284 -0
  168. adam/sql/lark_parser.py +604 -0
  169. adam/sql/sql_completer.py +118 -0
  170. adam/sql/sql_state_machine.py +630 -0
  171. adam/sql/term_completer.py +76 -0
  172. adam/sso/authn_ad.py +7 -9
  173. adam/sso/authn_okta.py +4 -6
  174. adam/sso/cred_cache.py +4 -6
  175. adam/sso/idp.py +10 -13
  176. adam/utils.py +539 -11
  177. adam/utils_athena.py +145 -0
  178. adam/utils_audits.py +102 -0
  179. adam/utils_issues.py +32 -0
  180. adam/utils_k8s/__init__.py +0 -0
  181. adam/utils_k8s/app_clusters.py +28 -0
  182. adam/utils_k8s/app_pods.py +36 -0
  183. adam/utils_k8s/cassandra_clusters.py +44 -0
  184. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
  185. adam/{k8s_utils → utils_k8s}/custom_resources.py +16 -17
  186. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  187. adam/{k8s_utils → utils_k8s}/ingresses.py +2 -2
  188. adam/{k8s_utils → utils_k8s}/jobs.py +7 -11
  189. adam/utils_k8s/k8s.py +96 -0
  190. adam/{k8s_utils → utils_k8s}/kube_context.py +3 -3
  191. adam/{k8s_utils → utils_k8s}/pods.py +132 -83
  192. adam/{k8s_utils → utils_k8s}/secrets.py +7 -3
  193. adam/{k8s_utils → utils_k8s}/service_accounts.py +5 -4
  194. adam/{k8s_utils → utils_k8s}/services.py +2 -2
  195. adam/{k8s_utils → utils_k8s}/statefulsets.py +9 -16
  196. adam/utils_local.py +4 -0
  197. adam/utils_net.py +24 -0
  198. adam/utils_repl/__init__.py +0 -0
  199. adam/utils_repl/appendable_completer.py +6 -0
  200. adam/utils_repl/automata_completer.py +48 -0
  201. adam/utils_repl/repl_completer.py +172 -0
  202. adam/utils_repl/state_machine.py +173 -0
  203. adam/utils_sqlite.py +137 -0
  204. adam/version.py +1 -1
  205. {kaqing-2.0.14.dist-info → kaqing-2.0.189.dist-info}/METADATA +1 -1
  206. kaqing-2.0.189.dist-info/RECORD +253 -0
  207. kaqing-2.0.189.dist-info/top_level.txt +2 -0
  208. teddy/__init__.py +0 -0
  209. teddy/lark_parser.py +436 -0
  210. teddy/lark_parser2.py +618 -0
  211. adam/commands/app.py +0 -67
  212. adam/commands/bash.py +0 -87
  213. adam/commands/cp.py +0 -95
  214. adam/commands/cql_utils.py +0 -53
  215. adam/commands/devices.py +0 -89
  216. adam/commands/postgres/postgres_session.py +0 -247
  217. adam/commands/reaper/reaper_session.py +0 -159
  218. adam/commands/show/show_app_actions.py +0 -53
  219. adam/commands/show/show_commands.py +0 -61
  220. adam/commands/show/show_repairs.py +0 -47
  221. adam/k8s_utils/cassandra_clusters.py +0 -48
  222. kaqing-2.0.14.dist-info/RECORD +0 -167
  223. kaqing-2.0.14.dist-info/top_level.txt +0 -1
  224. /adam/{k8s_utils → commands/app}/__init__.py +0 -0
  225. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  226. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  227. {kaqing-2.0.14.dist-info → kaqing-2.0.189.dist-info}/WHEEL +0 -0
  228. {kaqing-2.0.14.dist-info → kaqing-2.0.189.dist-info}/entry_points.txt +0 -0
adam/utils_k8s/k8s.py ADDED
@@ -0,0 +1,96 @@
1
+ from collections.abc import Callable
2
+ import inspect
3
+ import re
4
+ import portforward
5
+
6
+ from adam.commands.command import InvalidStateException
7
+ from adam.repl_state import ReplState
8
+ from adam.utils import log2
9
+ from adam.utils_k8s.kube_context import KubeContext
10
+
11
+ class PortForwardHandler:
12
+ connections: dict[str, int] = {}
13
+
14
+ def __init__(self, state: ReplState, local_port: int, svc_or_pod: Callable[[bool],str], target_port: int):
15
+ self.state = state
16
+ self.local_port = local_port
17
+ self.svc_or_pod = svc_or_pod
18
+ self.target_port = target_port
19
+ self.forward_connection = None
20
+ self.pod = None
21
+
22
+ def __enter__(self) -> tuple[str, str]:
23
+ state = self.state
24
+
25
+ if not self.svc_or_pod:
26
+ log2('No service or pod found.')
27
+
28
+ raise InvalidStateException(state)
29
+
30
+ if KubeContext.in_cluster():
31
+ svc_name = self.svc_or_pod(True)
32
+ if not svc_name:
33
+ log2('No service found.')
34
+
35
+ raise InvalidStateException(state)
36
+
37
+ # cs-a526330d23-cs-a526330d23-default-sts-0 ->
38
+ # curl http://cs-a526330d23-cs-a526330d23-reaper-service.stgawsscpsr.svc.cluster.local:8080
39
+ groups = re.match(r'^(.*?-.*?-.*?-.*?-).*', state.sts)
40
+ if groups:
41
+ svc = f'{groups[1]}{svc_name}.{state.namespace}.svc.cluster.local:{self.target_port}'
42
+ return (svc, svc)
43
+ else:
44
+ raise InvalidStateException(state)
45
+ else:
46
+ pod = self.svc_or_pod(False)
47
+ if not pod:
48
+ log2('No pod found.')
49
+
50
+ raise InvalidStateException(state)
51
+
52
+ self.pod = pod
53
+
54
+ # pf = portforward.forward(state.namespace, pod, self.local_port + 1, self.target_port, log_level=portforward.LogLevel.DEBUG)
55
+ # print(inspect.getsource(pf.__enter__))
56
+ # print('test portforward START', state.namespace, pod, self.local_port + 1, self.target_port, pf.__enter__)
57
+ # with pf:
58
+ # print('test portforward BODY')
59
+ # print('test portforward OK')
60
+
61
+ self.forward_connection = portforward.forward(state.namespace, pod, self.local_port, self.target_port)
62
+ if self.inc_connection_cnt() == 1:
63
+ self.forward_connection.__enter__()
64
+
65
+ return (f'localhost:{self.local_port}', f'{pod}:{self.target_port}')
66
+
67
+ def __exit__(self, exc_type, exc_val, exc_tb):
68
+ if self.forward_connection:
69
+ if not self.dec_connection_cnt():
70
+ return self.forward_connection.__exit__(exc_type, exc_val, exc_tb)
71
+
72
+ return False
73
+
74
+ def inc_connection_cnt(self):
75
+ id = self.connection_id(self.pod)
76
+ if id not in PortForwardHandler.connections:
77
+ PortForwardHandler.connections[id] = 1
78
+ else:
79
+ PortForwardHandler.connections[id] += 1
80
+
81
+ return PortForwardHandler.connections[id]
82
+
83
+ def dec_connection_cnt(self):
84
+ id = self.connection_id(self.pod)
85
+ if id not in PortForwardHandler.connections:
86
+ PortForwardHandler.connections[id] = 0
87
+ elif PortForwardHandler.connections[id] > 0:
88
+ PortForwardHandler.connections[id] -= 1
89
+
90
+ return PortForwardHandler.connections[id]
91
+
92
+ def connection_id(self, pod: str):
93
+ return f'{self.local_port}:{pod}:{self.target_port}'
94
+
95
+ def port_forwarding(state: ReplState, local_port: int, svc_or_pod: Callable[[bool],str], target_port: int):
96
+ return PortForwardHandler(state, local_port, svc_or_pod, target_port)
@@ -3,7 +3,7 @@ import re
3
3
  from kubernetes import config as kconfig
4
4
 
5
5
  from adam.config import Config
6
- from adam.utils import idp_token_from_env, lines_to_tabular, log2
6
+ from adam.utils import idp_token_from_env, log2, tabulize
7
7
 
8
8
  class KubeContext:
9
9
  _in_cluster = False
@@ -56,7 +56,7 @@ class KubeContext:
56
56
  log2('Use -v <key>=<value> format.')
57
57
  log2()
58
58
  lines = [f'{key}\t{Config().get(key, None)}' for key in Config().keys()]
59
- log2(lines_to_tabular(lines, separator='\t'))
59
+ tabulize(lines, separator='\t', to=2)
60
60
 
61
61
  for p in param_ovrs:
62
62
  tokens = p.split('=')
@@ -105,4 +105,4 @@ class KubeContext:
105
105
  return s or Config().is_debug()
106
106
 
107
107
  def show_parallelism():
108
- return Config().get('debug.show-parallelism', False)
108
+ return Config().get('debugs.show-parallelism', False)
@@ -1,23 +1,29 @@
1
1
  from collections.abc import Callable
2
- from concurrent.futures import ThreadPoolExecutor, as_completed
2
+ from datetime import datetime
3
+ import os
3
4
  import sys
4
5
  import time
5
- from typing import TypeVar, cast
6
+ from typing import TypeVar
6
7
  from kubernetes import client
7
8
  from kubernetes.stream import stream
8
- from kubernetes.stream.ws_client import ERROR_CHANNEL
9
+ from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
9
10
 
10
11
  from adam.config import Config
11
- from adam.k8s_utils.volumes import ConfigMapMount
12
+ from adam.utils_k8s.volumes import ConfigMapMount
12
13
  from adam.pod_exec_result import PodExecResult
13
- from adam.utils import elapsed_time, log2
14
+ from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_exc
15
+ from adam.utils_local import local_tmp_dir
14
16
  from .kube_context import KubeContext
15
17
 
18
+ from websocket._core import WebSocket
19
+
16
20
  T = TypeVar('T')
17
21
  _TEST_POD_EXEC_OUTS: PodExecResult = None
18
22
 
19
23
  # utility collection on pods; methods are all static
20
24
  class Pods:
25
+ _TEST_POD_CLOSE_SOCKET: bool = False
26
+
21
27
  def set_test_pod_exec_outs(outs: PodExecResult):
22
28
  global _TEST_POD_EXEC_OUTS
23
29
  _TEST_POD_EXEC_OUTS = outs
@@ -25,11 +31,9 @@ class Pods:
25
31
  return _TEST_POD_EXEC_OUTS
26
32
 
27
33
  def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
28
- try:
34
+ with log_exc(lambda e: "Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e):
29
35
  v1 = client.CoreV1Api()
30
- api_response = v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
31
- except Exception as e:
32
- log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
36
+ v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
33
37
 
34
38
  def delete_with_selector(namespace: str, label_selector: str, grace_period_seconds: int = None):
35
39
  v1 = client.CoreV1Api()
@@ -38,61 +42,25 @@ class Pods:
38
42
  for i in ret.items:
39
43
  v1.delete_namespaced_pod(name=i.metadata.name, namespace=namespace, grace_period_seconds=grace_period_seconds)
40
44
 
41
- def on_pods(pods: list[str],
42
- namespace: str,
43
- body: Callable[[ThreadPoolExecutor, str, str, bool], T],
44
- post: Callable[[T], T] = None,
45
- action: str = 'action', max_workers=0, show_out=True) -> list[T]:
46
- show_out = KubeContext.show_out(show_out)
47
-
45
+ def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, action: str = 'action'):
48
46
  if not max_workers:
49
47
  max_workers = Config().action_workers(action, 0)
50
- if max_workers > 0:
51
- # if parallel, node sampling is suppressed
52
- if KubeContext.show_parallelism():
53
- log2(f'Executing on all nodes from statefulset in parallel...')
54
- start_time = time.time()
55
- try:
56
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
57
- # disable stdout from the pod_exec, then show the output in a for loop
58
- futures = [body(executor, pod, namespace, show_out) for pod in pods]
59
- if len(futures) == 0:
60
- return cast(list[T], [])
61
-
62
- rs = [future.result() for future in as_completed(futures)]
63
- if post:
64
- rs = [post(r, show_out=show_out) for r in rs]
65
-
66
- return rs
67
- finally:
68
- if KubeContext.show_parallelism():
69
- log2(f"Parallel {action} elapsed time: {elapsed_time(start_time)} with {max_workers} workers")
70
- else:
71
- results: list[T] = []
72
-
48
+ if samples == sys.maxsize:
73
49
  samples = Config().action_node_samples(action, sys.maxsize)
74
- l = min(len(pods), samples)
75
- adj = 'all'
76
- if l < len(pods):
77
- adj = f'{l} sample'
78
- if show_out:
79
- log2(f'Executing on {adj} nodes from statefulset...')
80
- for pod_name in pods:
81
- try:
82
- result = body(None, pod_name, namespace, show_out)
83
- if post:
84
- result = post(result, show_out=show_out)
85
- results.append(result)
86
- if result:
87
- l -= 1
88
- if not l:
89
- break
90
- except Exception as e:
91
- log2(e)
92
-
93
- return results
94
-
95
- def exec(pod_name: str, container: str, namespace: str, command: str, show_out = True, throw_err = False, interaction: Callable[[any, list[str]], any] = None):
50
+
51
+ return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
52
+
53
+ def exec(pod_name: str,
54
+ container: str,
55
+ namespace: str,
56
+ command: str,
57
+ show_out = True,
58
+ throw_err = False,
59
+ shell = '/bin/sh',
60
+ backgrounded = False,
61
+ log_file = None,
62
+ interaction: Callable[[any, list[str]], any] = None,
63
+ env_prefix: str = None):
96
64
  if _TEST_POD_EXEC_OUTS:
97
65
  return _TEST_POD_EXEC_OUTS
98
66
 
@@ -100,12 +68,32 @@ class Pods:
100
68
 
101
69
  api = client.CoreV1Api()
102
70
 
103
- exec_command = ["/bin/sh", "-c", command]
104
- k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {command}'
105
- if show_out:
106
- print(k_command)
71
+ tty = True
72
+ exec_command = [shell, '-c', command]
73
+ if env_prefix:
74
+ exec_command = [shell, '-c', f'{env_prefix} {command}']
107
75
 
108
- resp = stream(
76
+ if backgrounded or command.endswith(' &'):
77
+ # should be false for starting a background process
78
+ tty = False
79
+
80
+ if Config().get('repl.background-process.auto-nohup', True):
81
+ command = command.strip(' &')
82
+ cmd_name = ''
83
+ if command.startswith('nodetool '):
84
+ cmd_name = f".{'_'.join(command.split(' ')[5:])}"
85
+
86
+ if not log_file:
87
+ log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
88
+ command = f"nohup {command} > {log_file} 2>&1 &"
89
+ if env_prefix:
90
+ command = f'{env_prefix} {command}'
91
+ exec_command = [shell, '-c', command]
92
+
93
+ k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
94
+ debug(k_command)
95
+
96
+ resp: WSClient = stream(
109
97
  api.connect_get_namespaced_pod_exec,
110
98
  pod_name,
111
99
  namespace,
@@ -114,10 +102,11 @@ class Pods:
114
102
  stderr=True,
115
103
  stdin=True,
116
104
  stdout=True,
117
- tty=True,
105
+ tty=tty,
118
106
  _preload_content=False,
119
107
  )
120
108
 
109
+ s: WebSocket = resp.sock
121
110
  stdout = []
122
111
  stderr = []
123
112
  error_output = None
@@ -136,11 +125,9 @@ class Pods:
136
125
  stderr.append(frag)
137
126
  if show_out: print(frag, end="")
138
127
 
139
- try:
128
+ with log_exc():
140
129
  # get the exit code from server
141
130
  error_output = resp.read_channel(ERROR_CHANNEL)
142
- except Exception:
143
- pass
144
131
  except Exception as e:
145
132
  if throw_err:
146
133
  raise e
@@ -148,8 +135,54 @@ class Pods:
148
135
  log2(e)
149
136
  finally:
150
137
  resp.close()
138
+ if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
139
+ with log_exc():
140
+ s.sock.close()
141
+
142
+ return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
151
143
 
152
- return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output)
144
+ def read_file(pod_name: str, container: str, namespace: str, file_path: str):
145
+ v1 = client.CoreV1Api()
146
+
147
+ resp = stream(
148
+ v1.connect_get_namespaced_pod_exec,
149
+ name=pod_name,
150
+ namespace=namespace,
151
+ container=container,
152
+ command=["cat", file_path],
153
+ stderr=True, stdin=False,
154
+ stdout=True, tty=False,
155
+ _preload_content=False, # Important for streaming
156
+ )
157
+
158
+ s: WebSocket = resp.sock
159
+ try:
160
+ while resp.is_open():
161
+ resp.update(timeout=1)
162
+ if resp.peek_stdout():
163
+ yield resp.read_stdout()
164
+
165
+ with log_exc():
166
+ # get the exit code from server
167
+ error_output = resp.read_channel(ERROR_CHANNEL)
168
+ except Exception as e:
169
+ raise e
170
+ finally:
171
+ resp.close()
172
+ if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
173
+ with log_exc():
174
+ s.sock.close()
175
+
176
+ def download_file(pod_name: str, container: str, namespace: str, from_path: str, to_path: str = None):
177
+ if not to_path:
178
+ to_path = f'{local_tmp_dir()}/{os.path.basename(from_path)}'
179
+
180
+ bytes = Pods.read_file(pod_name, container, namespace, from_path)
181
+ with open(to_path, 'wb') as f:
182
+ for item in GeneratorStream(bytes):
183
+ f.write(item)
184
+
185
+ return to_path
153
186
 
154
187
  def get_container(namespace: str, pod_name: str, container_name: str):
155
188
  pod = Pods.get(namespace, pod_name)
@@ -233,17 +266,33 @@ class Pods:
233
266
  ))
234
267
  )
235
268
 
236
- def wait_for_running(namespace: str, pod_name: str, msg: str=None, label_selector: str = None):
237
- msged = False
238
-
239
- while (Pods.get_with_selector(namespace, label_selector) if label_selector else Pods.get(namespace, pod_name)).status.phase != 'Running':
240
- if not msged:
241
- if not msg:
242
- msg = f'Waiting for the {pod_name} pod to start up...'
243
- log2(msg, nl=False)
244
- msged = True
245
- time.sleep(5)
246
- log2(' OK')
269
+ def wait_for_running(namespace: str, pod_name: str, msg: str = None, label_selector: str = None):
270
+ cnt = 2
271
+ while (cnt < 302 and Pods.get_with_selector(namespace, label_selector) if label_selector else Pods.get(namespace, pod_name)).status.phase != 'Running':
272
+ if not msg:
273
+ msg = f'Waiting for the {pod_name} pod to start up.'
274
+
275
+ max_len = len(msg) + 3
276
+ mod = cnt % 3
277
+ padded = ''
278
+ if mod == 0:
279
+ padded = f'\r{msg}'.ljust(max_len)
280
+ elif mod == 1:
281
+ padded = f'\r{msg}.'.ljust(max_len)
282
+ else:
283
+ padded = f'\r{msg}..'.ljust(max_len)
284
+ log2(padded, nl=False)
285
+ cnt += 1
286
+ time.sleep(1)
287
+
288
+ log2(f'\r{msg}..'.ljust(max_len), nl=False)
289
+ if cnt < 302:
290
+ log2(' OK')
291
+ else:
292
+ log2(' Timed Out')
247
293
 
248
294
  def completed(namespace: str, pod_name: str):
249
- return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
295
+ return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
296
+
297
+ def log_prefix():
298
+ return Config().get('log-prefix', '/tmp/qing')
@@ -1,15 +1,19 @@
1
1
  import base64
2
+ import functools
2
3
  import re
3
4
  from typing import cast
4
5
  from kubernetes import client
5
6
  from kubernetes.client import V1Secret
6
7
 
7
8
  from adam.config import Config
8
- from adam.utils import log2
9
+ from adam.utils import log2, wait_log
9
10
 
10
11
  # utility collection on secrets; methods are all static
11
12
  class Secrets:
13
+ @functools.lru_cache()
12
14
  def list_secrets(namespace: str = None, name_pattern: str = None):
15
+ wait_log('Inspecting Cassandra Instances...')
16
+
13
17
  secrets_names = []
14
18
 
15
19
  v1 = client.CoreV1Api()
@@ -35,14 +39,14 @@ class Secrets:
35
39
 
36
40
  return secrets_names
37
41
 
38
- def get_user_pass(ss_name: str, namespace: str, secret_path: str = 'cql.secret'):
42
+ def get_user_pass(sts_or_pod_name: str, namespace: str, secret_path: str = 'cql.secret'):
39
43
  # cs-d0767a536f-cs-d0767a536f-default-sts ->
40
44
  # cs-d0767a536f-superuser
41
45
  # cs-d0767a536f-reaper-ui
42
46
  user = 'superuser'
43
47
  if secret_path == 'reaper.secret':
44
48
  user = 'reaper-ui'
45
- groups = re.match(Config().get(f'{secret_path}.cluster-regex', r'(.*?-.*?)-.*'), ss_name)
49
+ groups = re.match(Config().get(f'{secret_path}.cluster-regex', r'(.*?-.*?)-.*'), sts_or_pod_name)
46
50
  secret_name = Config().get(f'{secret_path}.name', '{cluster}-' + user).replace('{cluster}', groups[1], 1)
47
51
 
48
52
  secret = Secrets.get_data(namespace, secret_name)
@@ -1,6 +1,7 @@
1
1
  from kubernetes import client, config
2
2
 
3
3
  from adam.config import Config
4
+ from adam.utils import debug
4
5
 
5
6
  # utility collection on service accounts; methods are all static
6
7
  class ServiceAccounts:
@@ -37,7 +38,7 @@ class ServiceAccounts:
37
38
  namespace=namespace,
38
39
  body=service_account
39
40
  )
40
- Config().debug(f"Service Account '{api_response.metadata.name}' created in namespace '{namespace}'.")
41
+ debug(f"Service Account '{api_response.metadata.name}' created in namespace '{namespace}'.")
41
42
 
42
43
  def delete_service_account(namespace: str, label_selector: str) -> list:
43
44
  refs = []
@@ -45,7 +46,7 @@ class ServiceAccounts:
45
46
  v1 = client.CoreV1Api()
46
47
  sas = v1.list_namespaced_service_account(namespace=namespace, label_selector=label_selector).items
47
48
  for sa in sas:
48
- Config().debug(f'delete {sa.metadata.name}')
49
+ debug(f'delete {sa.metadata.name}')
49
50
  v1.delete_namespaced_service_account(name=sa.metadata.name, namespace=namespace)
50
51
  refs.append(sa)
51
52
 
@@ -102,7 +103,7 @@ class ServiceAccounts:
102
103
  v1_rbac = client.RbacAuthorizationV1Api()
103
104
  cluster_role_bindings = v1_rbac.list_namespaced_role_binding(namespace=namespace, label_selector=label_selector).items
104
105
  for binding in cluster_role_bindings:
105
- Config().debug(f'delete {binding.metadata.name}')
106
+ debug(f'delete {binding.metadata.name}')
106
107
  v1_rbac.delete_namespaced_role_binding(name=binding.metadata.name, namespace=namespace)
107
108
  refs.append(binding)
108
109
 
@@ -162,7 +163,7 @@ class ServiceAccounts:
162
163
  v1_rbac = client.RbacAuthorizationV1Api()
163
164
  cluster_role_bindings = v1_rbac.list_cluster_role_binding(label_selector=label_selector).items
164
165
  for binding in cluster_role_bindings:
165
- Config().debug(f'delete {binding.metadata.name}')
166
+ debug(f'delete {binding.metadata.name}')
166
167
  v1_rbac.delete_cluster_role_binding(binding.metadata.name)
167
168
  refs.append(binding)
168
169
 
@@ -2,7 +2,7 @@ from typing import List
2
2
  from kubernetes import client
3
3
 
4
4
  from adam.config import Config
5
- from adam.utils import log2
5
+ from adam.utils import debug, log2
6
6
 
7
7
  from .kube_context import KubeContext
8
8
 
@@ -71,7 +71,7 @@ class Services:
71
71
  namespace=namespace,
72
72
  body=delete_options
73
73
  )
74
- Config().debug(f"200 Service '{name}' in namespace '{namespace}' deleted successfully.")
74
+ debug(f"200 Service '{name}' in namespace '{namespace}' deleted successfully.")
75
75
  except client.ApiException as e:
76
76
  log2(f"Error deleting Service '{name}': {e}")
77
77
  except Exception as e:
@@ -1,12 +1,9 @@
1
- from collections.abc import Callable
2
- from concurrent.futures import ThreadPoolExecutor
3
1
  from datetime import datetime
4
2
  import functools
5
3
  import re
6
4
  from typing import List, TypeVar, cast
7
5
  from kubernetes import client
8
6
 
9
- from .pods import Pods
10
7
  from .kube_context import KubeContext
11
8
  from adam.utils import log2
12
9
 
@@ -24,11 +21,12 @@ class StatefulSets:
24
21
 
25
22
  return statefulsets.items
26
23
 
24
+ @functools.lru_cache()
27
25
  def list_sts_name_and_ns():
28
26
  return [(statefulset.metadata.name, statefulset.metadata.namespace) for statefulset in StatefulSets.list_sts()]
29
27
 
30
- def list_sts_names(show_namespace = True):
31
- if show_namespace:
28
+ def list_sts_names():
29
+ if not KubeContext.in_cluster_namespace():
32
30
  return [f"{sts}@{ns}" for sts, ns in StatefulSets.list_sts_name_and_ns()]
33
31
  else:
34
32
  return [f"{sts}" for sts, _ in StatefulSets.list_sts_name_and_ns()]
@@ -57,18 +55,12 @@ class StatefulSets:
57
55
 
58
56
  return statefulset_pods
59
57
 
60
- def on_cluster(statefulset: str,
61
- namespace: str,
62
- body: Callable[[ThreadPoolExecutor, str, str, bool], T],
63
- post: Callable[[T], T] = None,
64
- action: str = 'action', max_workers=0, show_out=True) -> list[T]:
65
- pods = StatefulSets.pod_names(statefulset, namespace)
66
-
67
- return Pods.on_pods(pods, namespace, body, post=post, action=action, max_workers=max_workers, show_out=show_out)
68
-
69
58
  @functools.lru_cache()
70
- def pod_names(ss: str, ns: str):
71
- return [pod.metadata.name for pod in StatefulSets.pods(ss, ns)]
59
+ def pod_names(sts: str, ns: str):
60
+ if not sts:
61
+ return []
62
+
63
+ return [pod.metadata.name for pod in StatefulSets.pods(sts, ns)]
72
64
 
73
65
  def restarted_at(ss: str, ns: str):
74
66
  # returns timestamp and if being rolled out
@@ -91,6 +83,7 @@ class StatefulSets:
91
83
 
92
84
  return restarted, False
93
85
 
86
+ @functools.lru_cache()
94
87
  def get_datacenter(sts: str, ns: str) -> str:
95
88
  v1 = client.AppsV1Api()
96
89
  namespace = ns
adam/utils_local.py ADDED
@@ -0,0 +1,4 @@
1
+ from adam.config import Config
2
+
3
+ def local_tmp_dir():
4
+ return Config().get('local-tmp-dir', '/tmp/qing-db')
adam/utils_net.py ADDED
@@ -0,0 +1,24 @@
1
+ import socket
2
+
3
+ MY_HOST = None
4
+
5
+ def get_my_host():
6
+ global MY_HOST
7
+
8
+ if MY_HOST:
9
+ return MY_HOST
10
+
11
+ MY_HOST = get_ip_from_hostname('host.docker.internal')
12
+ if not MY_HOST:
13
+ MY_HOST = socket.gethostname()
14
+
15
+ if not MY_HOST:
16
+ MY_HOST = 'NA'
17
+
18
+ return MY_HOST
19
+
20
+ def get_ip_from_hostname(hostname):
21
+ try:
22
+ return socket.gethostbyname(hostname)
23
+ except socket.gaierror:
24
+ return None
File without changes
@@ -0,0 +1,6 @@
1
+ from abc import abstractmethod
2
+
3
+ class AppendableCompleter:
4
+ @abstractmethod
5
+ def append_completions(self, key: str, value: dict[str, any]):
6
+ pass
@@ -0,0 +1,48 @@
1
+ from typing import Generic, Iterable, TypeVar
2
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, WordCompleter
3
+ from prompt_toolkit.document import Document
4
+
5
+ from adam.utils_repl.state_machine import StateMachine, State
6
+
7
+ __all__ = [
8
+ "AutomataCompleter",
9
+ ]
10
+
11
+ T = TypeVar('T')
12
+
13
+ class AutomataCompleter(Completer, Generic[T]):
14
+ def __init__(self,
15
+ state_machine: StateMachine,
16
+ first_term: str = '',
17
+ debug = False):
18
+ super().__init__()
19
+ self.machine = state_machine
20
+ self.first_term = first_term
21
+ self.debug = debug
22
+
23
+ def get_completions(
24
+ self, document: Document, complete_event: CompleteEvent
25
+ ) -> Iterable[Completion]:
26
+ text = document.text_before_cursor.lstrip()
27
+ state = ''
28
+ if self.first_term:
29
+ text = f'{self.first_term} {text}'
30
+
31
+ completer: Completer = None
32
+ state: State = self.machine.traverse_tokens(self.tokens(text), State(state))
33
+ if self.debug:
34
+ print('\n =>', state.state if isinstance(state, State) else '')
35
+
36
+ if state.state in self.machine.suggestions:
37
+ if completer := self.suggestions_completer(state, self.machine.suggestions[state.state].strip(' ')):
38
+ for c in completer.get_completions(document, complete_event):
39
+ yield c
40
+
41
+ def tokens(self, text: str) -> list[T]:
42
+ return text.split(' ')
43
+
44
+ def suggestions_completer(self, _: State, suggestions: str) -> list[str]:
45
+ if not suggestions:
46
+ return None
47
+
48
+ return WordCompleter(suggestions.split(','))