kaqing 2.0.110__py3-none-any.whl → 2.0.214__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 (251) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -12
  3. adam/apps.py +18 -4
  4. adam/batch.py +19 -19
  5. adam/checks/check_utils.py +16 -46
  6. adam/checks/cpu.py +7 -1
  7. adam/checks/cpu_metrics.py +52 -0
  8. adam/checks/disk.py +2 -3
  9. adam/columns/columns.py +3 -1
  10. adam/columns/cpu.py +3 -1
  11. adam/columns/cpu_metrics.py +22 -0
  12. adam/columns/memory.py +3 -4
  13. adam/commands/__init__.py +24 -0
  14. adam/commands/app/app.py +38 -0
  15. adam/commands/{app_ping.py → app/app_ping.py} +7 -13
  16. adam/commands/{login.py → app/login.py} +22 -24
  17. adam/commands/app/show_app_actions.py +49 -0
  18. adam/commands/{show → app}/show_app_id.py +8 -11
  19. adam/commands/{show → app}/show_app_queues.py +7 -14
  20. adam/commands/app/show_login.py +56 -0
  21. adam/commands/app/utils_app.py +106 -0
  22. adam/commands/audit/audit.py +22 -40
  23. adam/commands/audit/audit_repair_tables.py +15 -19
  24. adam/commands/audit/audit_run.py +15 -22
  25. adam/commands/audit/completions_l.py +15 -0
  26. adam/commands/audit/show_last10.py +4 -18
  27. adam/commands/audit/show_slow10.py +4 -17
  28. adam/commands/audit/show_top10.py +4 -16
  29. adam/commands/audit/utils_show_top10.py +15 -3
  30. adam/commands/bash/__init__.py +5 -0
  31. adam/commands/bash/bash.py +36 -0
  32. adam/commands/bash/bash_completer.py +93 -0
  33. adam/commands/bash/utils_bash.py +16 -0
  34. adam/commands/cassandra/__init__.py +0 -0
  35. adam/commands/cassandra/download_cassandra_log.py +45 -0
  36. adam/commands/{restart.py → cassandra/restart_cluster.py} +12 -26
  37. adam/commands/cassandra/restart_node.py +51 -0
  38. adam/commands/cassandra/restart_nodes.py +47 -0
  39. adam/commands/{rollout.py → cassandra/rollout.py} +20 -25
  40. adam/commands/cassandra/show_cassandra_repairs.py +37 -0
  41. adam/commands/cassandra/show_cassandra_status.py +117 -0
  42. adam/commands/{show → cassandra}/show_cassandra_version.py +5 -18
  43. adam/commands/cassandra/show_processes.py +50 -0
  44. adam/commands/cassandra/show_storage.py +44 -0
  45. adam/commands/{watch.py → cassandra/watch.py} +26 -29
  46. adam/commands/cli/__init__.py +0 -0
  47. adam/commands/{cli_commands.py → cli/cli_commands.py} +8 -4
  48. adam/commands/cli/clipboard_copy.py +86 -0
  49. adam/commands/cli/show_cli_commands.py +56 -0
  50. adam/commands/code.py +57 -0
  51. adam/commands/command.py +211 -40
  52. adam/commands/commands_utils.py +20 -27
  53. adam/commands/config/__init__.py +0 -0
  54. adam/commands/{param_get.py → config/param_get.py} +11 -14
  55. adam/commands/{param_set.py → config/param_set.py} +8 -12
  56. adam/commands/{show → config}/show_params.py +2 -5
  57. adam/commands/cql/alter_tables.py +66 -0
  58. adam/commands/cql/completions_c.py +29 -0
  59. adam/commands/cql/cqlsh.py +10 -32
  60. adam/commands/cql/utils_cql.py +306 -0
  61. adam/commands/debug/__init__.py +0 -0
  62. adam/commands/debug/debug.py +22 -0
  63. adam/commands/debug/debug_completes.py +35 -0
  64. adam/commands/debug/debug_timings.py +35 -0
  65. adam/commands/debug/show_offloaded_completes.py +45 -0
  66. adam/commands/deploy/code_start.py +7 -10
  67. adam/commands/deploy/code_stop.py +4 -21
  68. adam/commands/deploy/code_utils.py +3 -3
  69. adam/commands/deploy/deploy.py +4 -27
  70. adam/commands/deploy/deploy_frontend.py +14 -17
  71. adam/commands/deploy/deploy_pg_agent.py +3 -6
  72. adam/commands/deploy/deploy_pod.py +65 -73
  73. adam/commands/deploy/deploy_utils.py +14 -24
  74. adam/commands/deploy/undeploy.py +4 -27
  75. adam/commands/deploy/undeploy_frontend.py +4 -7
  76. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  77. adam/commands/deploy/undeploy_pod.py +11 -12
  78. adam/commands/devices/__init__.py +0 -0
  79. adam/commands/devices/device.py +149 -0
  80. adam/commands/devices/device_app.py +163 -0
  81. adam/commands/devices/device_auit_log.py +49 -0
  82. adam/commands/devices/device_cass.py +179 -0
  83. adam/commands/devices/device_export.py +87 -0
  84. adam/commands/devices/device_postgres.py +160 -0
  85. adam/commands/devices/devices.py +25 -0
  86. adam/commands/diag/__init__.py +0 -0
  87. adam/commands/{check.py → diag/check.py} +16 -25
  88. adam/commands/diag/generate_report.py +52 -0
  89. adam/commands/diag/issues.py +43 -0
  90. adam/commands/exit.py +1 -4
  91. adam/commands/export/__init__.py +0 -0
  92. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  93. adam/commands/export/clean_up_export_sessions.py +39 -0
  94. adam/commands/export/completions_x.py +11 -0
  95. adam/commands/export/download_export_session.py +40 -0
  96. adam/commands/export/drop_export_database.py +39 -0
  97. adam/commands/export/drop_export_databases.py +37 -0
  98. adam/commands/export/export.py +37 -0
  99. adam/commands/export/export_databases.py +251 -0
  100. adam/commands/export/export_select.py +34 -0
  101. adam/commands/export/export_sessions.py +210 -0
  102. adam/commands/export/export_use.py +49 -0
  103. adam/commands/export/export_x_select.py +48 -0
  104. adam/commands/export/exporter.py +419 -0
  105. adam/commands/export/import_files.py +44 -0
  106. adam/commands/export/import_session.py +40 -0
  107. adam/commands/export/importer.py +81 -0
  108. adam/commands/export/importer_athena.py +157 -0
  109. adam/commands/export/importer_sqlite.py +78 -0
  110. adam/commands/export/show_column_counts.py +45 -0
  111. adam/commands/export/show_export_databases.py +39 -0
  112. adam/commands/export/show_export_session.py +39 -0
  113. adam/commands/export/show_export_sessions.py +37 -0
  114. adam/commands/export/utils_export.py +366 -0
  115. adam/commands/fs/__init__.py +0 -0
  116. adam/commands/fs/cat.py +36 -0
  117. adam/commands/fs/cat_local.py +42 -0
  118. adam/commands/fs/cd.py +41 -0
  119. adam/commands/fs/download_file.py +47 -0
  120. adam/commands/fs/find_files.py +51 -0
  121. adam/commands/fs/find_processes.py +76 -0
  122. adam/commands/fs/head.py +36 -0
  123. adam/commands/fs/ls.py +41 -0
  124. adam/commands/fs/ls_local.py +40 -0
  125. adam/commands/fs/pwd.py +45 -0
  126. adam/commands/fs/rm.py +18 -0
  127. adam/commands/fs/rm_downloads.py +39 -0
  128. adam/commands/fs/rm_logs.py +38 -0
  129. adam/commands/{shell.py → fs/shell.py} +12 -4
  130. adam/commands/{show → fs}/show_adam.py +3 -3
  131. adam/commands/{show → fs}/show_host.py +1 -1
  132. adam/commands/help.py +5 -3
  133. adam/commands/intermediate_command.py +52 -0
  134. adam/commands/kubectl.py +38 -0
  135. adam/commands/medusa/medusa.py +4 -22
  136. adam/commands/medusa/medusa_backup.py +20 -27
  137. adam/commands/medusa/medusa_restore.py +35 -48
  138. adam/commands/medusa/medusa_show_backupjobs.py +16 -18
  139. adam/commands/medusa/medusa_show_restorejobs.py +13 -18
  140. adam/commands/medusa/utils_medusa.py +15 -0
  141. adam/commands/nodetool/__init__.py +0 -0
  142. adam/commands/{nodetool.py → nodetool/nodetool.py} +9 -20
  143. adam/commands/postgres/completions_p.py +22 -0
  144. adam/commands/postgres/postgres.py +47 -55
  145. adam/commands/postgres/postgres_databases.py +269 -0
  146. adam/commands/postgres/postgres_ls.py +5 -9
  147. adam/commands/postgres/postgres_preview.py +5 -9
  148. adam/commands/postgres/utils_postgres.py +80 -0
  149. adam/commands/preview_table.py +8 -44
  150. adam/commands/reaper/reaper.py +4 -27
  151. adam/commands/reaper/reaper_forward.py +49 -56
  152. adam/commands/reaper/reaper_forward_session.py +6 -0
  153. adam/commands/reaper/reaper_forward_stop.py +10 -16
  154. adam/commands/reaper/reaper_restart.py +7 -14
  155. adam/commands/reaper/reaper_run_abort.py +8 -33
  156. adam/commands/reaper/reaper_runs.py +43 -58
  157. adam/commands/reaper/reaper_runs_abort.py +29 -49
  158. adam/commands/reaper/reaper_schedule_activate.py +14 -33
  159. adam/commands/reaper/reaper_schedule_start.py +9 -33
  160. adam/commands/reaper/reaper_schedule_stop.py +9 -33
  161. adam/commands/reaper/reaper_schedules.py +4 -14
  162. adam/commands/reaper/reaper_status.py +8 -16
  163. adam/commands/reaper/utils_reaper.py +203 -0
  164. adam/commands/repair/repair.py +4 -22
  165. adam/commands/repair/repair_log.py +5 -11
  166. adam/commands/repair/repair_run.py +27 -34
  167. adam/commands/repair/repair_scan.py +32 -40
  168. adam/commands/repair/repair_stop.py +5 -12
  169. adam/commands/show.py +40 -0
  170. adam/config.py +5 -15
  171. adam/embedded_params.py +1 -1
  172. adam/log.py +4 -4
  173. adam/repl.py +83 -116
  174. adam/repl_commands.py +86 -45
  175. adam/repl_session.py +9 -1
  176. adam/repl_state.py +176 -40
  177. adam/sql/async_executor.py +62 -0
  178. adam/sql/lark_completer.py +286 -0
  179. adam/sql/lark_parser.py +604 -0
  180. adam/sql/qingl.lark +1076 -0
  181. adam/sql/sql_completer.py +52 -27
  182. adam/sql/sql_state_machine.py +131 -19
  183. adam/sso/authn_ad.py +6 -8
  184. adam/sso/authn_okta.py +4 -6
  185. adam/sso/cred_cache.py +4 -9
  186. adam/sso/idp.py +9 -12
  187. adam/utils.py +670 -31
  188. adam/utils_athena.py +145 -0
  189. adam/utils_audits.py +12 -103
  190. adam/utils_issues.py +32 -0
  191. adam/utils_k8s/app_clusters.py +35 -0
  192. adam/utils_k8s/app_pods.py +41 -0
  193. adam/utils_k8s/cassandra_clusters.py +35 -20
  194. adam/utils_k8s/cassandra_nodes.py +15 -6
  195. adam/utils_k8s/custom_resources.py +16 -17
  196. adam/utils_k8s/ingresses.py +2 -2
  197. adam/utils_k8s/jobs.py +7 -11
  198. adam/utils_k8s/k8s.py +96 -0
  199. adam/utils_k8s/kube_context.py +3 -6
  200. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +13 -4
  201. adam/utils_k8s/pods.py +159 -89
  202. adam/utils_k8s/secrets.py +4 -4
  203. adam/utils_k8s/service_accounts.py +5 -4
  204. adam/utils_k8s/services.py +2 -2
  205. adam/utils_k8s/statefulsets.py +6 -14
  206. adam/utils_local.py +80 -0
  207. adam/utils_net.py +4 -4
  208. adam/utils_repl/__init__.py +0 -0
  209. adam/utils_repl/appendable_completer.py +6 -0
  210. adam/utils_repl/automata_completer.py +48 -0
  211. adam/utils_repl/repl_completer.py +93 -0
  212. adam/utils_repl/state_machine.py +173 -0
  213. adam/utils_sqlite.py +132 -0
  214. adam/version.py +1 -1
  215. {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/METADATA +1 -1
  216. kaqing-2.0.214.dist-info/RECORD +272 -0
  217. kaqing-2.0.214.dist-info/top_level.txt +2 -0
  218. teddy/__init__.py +0 -0
  219. teddy/lark_parser.py +436 -0
  220. teddy/lark_parser2.py +618 -0
  221. adam/commands/alter_tables.py +0 -81
  222. adam/commands/app.py +0 -67
  223. adam/commands/bash.py +0 -150
  224. adam/commands/cd.py +0 -125
  225. adam/commands/cp.py +0 -95
  226. adam/commands/cql/cql_completions.py +0 -15
  227. adam/commands/cql/cql_utils.py +0 -112
  228. adam/commands/devices.py +0 -118
  229. adam/commands/issues.py +0 -75
  230. adam/commands/logs.py +0 -40
  231. adam/commands/ls.py +0 -146
  232. adam/commands/postgres/postgres_context.py +0 -239
  233. adam/commands/postgres/postgres_utils.py +0 -31
  234. adam/commands/postgres/psql_completions.py +0 -10
  235. adam/commands/pwd.py +0 -77
  236. adam/commands/reaper/reaper_session.py +0 -159
  237. adam/commands/report.py +0 -63
  238. adam/commands/show/show.py +0 -54
  239. adam/commands/show/show_app_actions.py +0 -56
  240. adam/commands/show/show_cassandra_status.py +0 -128
  241. adam/commands/show/show_commands.py +0 -61
  242. adam/commands/show/show_login.py +0 -63
  243. adam/commands/show/show_processes.py +0 -53
  244. adam/commands/show/show_repairs.py +0 -47
  245. adam/commands/show/show_storage.py +0 -52
  246. kaqing-2.0.110.dist-info/RECORD +0 -187
  247. kaqing-2.0.110.dist-info/top_level.txt +0 -1
  248. /adam/commands/{show → app}/__init__.py +0 -0
  249. /adam/commands/{nodetool_commands.py → nodetool/nodetool_commands.py} +0 -0
  250. {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/WHEEL +0 -0
  251. {kaqing-2.0.110.dist-info → kaqing-2.0.214.dist-info}/entry_points.txt +0 -0
adam/repl_state.py CHANGED
@@ -1,10 +1,10 @@
1
- import copy
1
+ from copy import copy
2
2
  from enum import Enum
3
3
  import re
4
- import traceback
5
4
  from typing import Callable
6
5
 
7
- from adam.commands.postgres.postgres_context import PostgresContext
6
+ from adam.utils_k8s.app_clusters import AppClusters
7
+ from adam.utils_k8s.app_pods import AppPods
8
8
  from adam.utils_k8s.cassandra_clusters import CassandraClusters
9
9
  from adam.utils_k8s.cassandra_nodes import CassandraNodes
10
10
  from adam.utils_k8s.kube_context import KubeContext
@@ -19,25 +19,8 @@ class BashSession:
19
19
  def pwd(self, state: 'ReplState'):
20
20
  command = f'cat /tmp/.qing-{self.session_id}'
21
21
 
22
- if state.pod:
23
- rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
24
- elif state.sts:
25
- rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
26
-
27
- dir = None
28
- for r in rs:
29
- if r.exit_code(): # if fails to read the session file, ignore
30
- continue
31
-
32
- dir0 = r.stdout.strip(' \r\n')
33
- if dir:
34
- if dir != dir0:
35
- log2('Inconsitent working dir found across multiple pods.')
36
- return None
37
- else:
38
- dir = dir0
39
-
40
- return dir
22
+ with device(state) as pods:
23
+ return pods.exec(command, action='bash', show_out=False)
41
24
 
42
25
  class RequiredState(Enum):
43
26
  CLUSTER = 'cluster'
@@ -46,20 +29,22 @@ class RequiredState(Enum):
46
29
  NAMESPACE = 'namespace'
47
30
  PG_DATABASE = 'pg_database'
48
31
  APP_APP = 'app_app'
32
+ EXPORT_DB = 'export_db'
49
33
 
50
34
  class ReplState:
51
35
  A = 'a'
52
36
  C = 'c'
53
37
  L = 'l'
54
38
  P = 'p'
39
+ X = 'x'
55
40
 
56
- ANY = [A, C, L, P]
57
- NON_L = [A, C, P]
41
+ ANY = [A, C, L, P, X]
42
+ NON_L = [A, C, P, X]
58
43
 
59
44
  def __init__(self, device: str = None,
60
45
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
61
46
  pg_path: str = None,
62
- app_env: str = None, app_app: str = None,
47
+ app_env: str = None, app_app: str = None, app_pod: str = None,
63
48
  in_repl = False, bash_session: BashSession = None, remote_dir = None):
64
49
  self.namespace = KubeContext.in_cluster_namespace()
65
50
 
@@ -69,12 +54,16 @@ class ReplState:
69
54
  self.pg_path = pg_path
70
55
  self.app_env = app_env
71
56
  self.app_app = app_app
57
+ self.app_pod = app_pod
72
58
  if namespace:
73
59
  self.namespace = namespace
74
60
  self.in_repl = in_repl
75
61
  self.bash_session = bash_session
76
62
  self.remote_dir = remote_dir
77
- # self.wait_log_flag = False
63
+ self.original_state: ReplState = None
64
+ self.pod_targetted = False
65
+
66
+ self.export_session: str = None
78
67
 
79
68
  if ns_sts:
80
69
  nn = ns_sts.split('@')
@@ -89,13 +78,51 @@ class ReplState:
89
78
  def __hash__(self):
90
79
  return hash((self.sts, self.pod))
91
80
 
81
+ def __str__(self):
82
+ msg = ''
83
+ if self.device == ReplState.P:
84
+ msg = f'{ReplState.P}:'
85
+ host, database = self.pg_host_n_database()
86
+ if database:
87
+ msg += database
88
+ elif host:
89
+ msg += host
90
+ elif self.device == ReplState.A:
91
+ msg = f'{ReplState.A}:'
92
+ if self.app_env:
93
+ msg += self.app_env
94
+ if self.app_app:
95
+ msg += f'/{self.app_app}'
96
+ if self.app_pod:
97
+ # azops88-c3-c3-k8sdeploy-appleader-001-79957cf5b6-9k4bw
98
+ group = re.match(r".*?-.*?-.*?-.*?-(.*?-.*?)-.*", self.app_pod)
99
+ msg += '/' + group[1]
100
+ elif self.device == ReplState.L:
101
+ msg = f'{ReplState.L}:'
102
+ elif self.device == ReplState.X:
103
+ msg = f'{ReplState.X}:'
104
+ if self.export_session:
105
+ msg += self.export_session
106
+ else:
107
+ msg = f'{ReplState.C}:'
108
+ if self.pod:
109
+ # cs-d0767a536f-cs-d0767a536f-default-sts-0
110
+ group = re.match(r".*?-.*?-(.*)", self.pod)
111
+ msg += group[1]
112
+ elif self.sts:
113
+ # cs-d0767a536f-cs-d0767a536f-default-sts
114
+ group = re.match(r".*?-.*?-(.*)", self.sts)
115
+ msg += group[1]
116
+
117
+ return msg
118
+
92
119
  def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
93
120
  state = self
94
121
 
95
122
  new_args = []
96
123
  for index, arg in enumerate(args):
97
124
  if index < args_to_check:
98
- state = copy.copy(state)
125
+ state = copy(state)
99
126
 
100
127
  s, n = KubeContext.is_sts_name(arg)
101
128
  if s:
@@ -133,9 +160,9 @@ class ReplState:
133
160
  new_args = []
134
161
  for index, arg in enumerate(args):
135
162
  if index < 6:
136
- state = copy.copy(state)
163
+ state = copy(state)
137
164
 
138
- groups = re.match(r'^([a|c|l|p]):(.*)$', arg)
165
+ groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
139
166
  if groups:
140
167
  if groups[1] == 'p':
141
168
  state.device = 'p'
@@ -154,6 +181,8 @@ class ReplState:
154
181
  state.namespace = ns
155
182
  elif groups[1] == 'l':
156
183
  state.device = 'l'
184
+ elif groups[1] == 'x':
185
+ state.device = 'x'
157
186
  else:
158
187
  state.device = 'a'
159
188
  if path := groups[2]:
@@ -196,8 +225,8 @@ class ReplState:
196
225
 
197
226
  return False
198
227
 
199
- devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P]]
200
- non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P]]
228
+ devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
229
+ non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
201
230
 
202
231
  first_error: Callable = None
203
232
  for r in non_devices:
@@ -285,8 +314,8 @@ class ReplState:
285
314
  if self.device != ReplState.P:
286
315
  return (False, None)
287
316
 
288
- pg: PostgresContext = PostgresContext.apply(self.namespace, self.pg_path)
289
- if not pg.db:
317
+ _, database = self.pg_host_n_database()
318
+ if not database:
290
319
  def error():
291
320
  if self.in_repl:
292
321
  log2('cd to a database first.')
@@ -310,7 +339,24 @@ class ReplState:
310
339
  display_help()
311
340
  return (False, error)
312
341
 
313
- elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P] and self.device != required:
342
+ elif required == RequiredState.EXPORT_DB:
343
+ if self.device not in [ReplState.C, ReplState.X]:
344
+ return (False, None)
345
+
346
+ if not self.export_session:
347
+ def error():
348
+ if self.in_repl:
349
+ if self.device == ReplState.C:
350
+ log2("Select an export database first with 'use' command.")
351
+ else:
352
+ log2('cd to an export database first.')
353
+ else:
354
+ log2('* export database is missing.')
355
+ log2()
356
+ display_help()
357
+ return (False, error)
358
+
359
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
314
360
  def error():
315
361
  if self.in_repl:
316
362
  log2(f'Switch to {required}: first.')
@@ -339,13 +385,103 @@ class ReplState:
339
385
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
340
386
 
341
387
  def enter_bash(self, bash_session: BashSession):
388
+ self.push()
389
+
342
390
  self.bash_session = bash_session
343
- if self.device != ReplState.C:
344
- self.device = ReplState.C
345
- log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
346
391
 
347
392
  def exit_bash(self):
348
- if self.bash_session and self.bash_session.device:
349
- self.device = self.bash_session.device
393
+ self.pop()
394
+ self.bash_session = None
395
+
396
+ def push(self, pod_targetted=False):
397
+ if not self.original_state:
398
+ self.original_state = copy(self)
399
+ self.pod_targetted = pod_targetted
400
+
401
+ def pop(self):
402
+ if o := self.original_state:
403
+ self.device = o.device
404
+ self.sts = o.sts
405
+ self.pod = o.pod
406
+ self.pg_path = o.pg_path
407
+ self.app_env = o.app_env
408
+ self.app_app = o.app_app
409
+ self.app_pod = o.app_pod
410
+ # self.export_session = o.export_session
411
+ self.namespace = o.namespace
412
+
413
+ self.original_state = None
414
+ self.pod_targetted = False
415
+
416
+ def pg_host_n_database(self):
417
+ host = None
418
+ database = None
419
+
420
+ if self.pg_path:
421
+ host_n_db = self.pg_path.split('/')
422
+ host = host_n_db[0]
423
+ if len(host_n_db) > 1:
424
+ database = host_n_db[1]
425
+
426
+ return host, database
427
+
428
+ def with_no_pod(self):
429
+ state1 = copy(self)
430
+ state1.pod = None
431
+
432
+ return state1
433
+
434
+ def with_pod(self, pod: str):
435
+ state1 = copy(self)
436
+ state1.pod = pod
437
+
438
+ return state1
439
+
440
+ class DevicePodService:
441
+ def __init__(self, handler: 'DeviceExecHandler'):
442
+ self.handler = handler
443
+
444
+ def exec(self, command: str, action='bash', show_out = True):
445
+ state = self.handler.state
446
+
447
+ rs = None
448
+ if state.device == ReplState.A and state.app_app:
449
+ if state.app_pod:
450
+ rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=show_out)]
451
+ else:
452
+ pods = AppPods.pod_names(state.namespace, state.app_env, state.app_app)
453
+ rs = AppClusters.exec(pods, state.namespace, command, show_out=show_out)
454
+ elif state.pod:
455
+ rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=show_out)]
456
+ elif state.sts:
457
+ rs = CassandraClusters.exec(state.sts, state.namespace, command, action=action, show_out=show_out)
458
+ # assume that pg-agent or ops pod is single pod
459
+
460
+ dir = None
461
+ if rs:
462
+ for r in rs:
463
+ if r.exit_code(): # if fails to read the session file, ignore
464
+ continue
465
+
466
+ dir0 = r.stdout.strip(' \r\n')
467
+ if dir:
468
+ if dir != dir0:
469
+ log2('Inconsitent working dir found across multiple pods.')
470
+ return None
471
+ else:
472
+ dir = dir0
473
+
474
+ return dir
475
+
476
+ class DeviceExecHandler:
477
+ def __init__(self, state: ReplState):
478
+ self.state = state
479
+
480
+ def __enter__(self):
481
+ return DevicePodService(self)
482
+
483
+ def __exit__(self, exc_type, exc_val, exc_tb):
484
+ return False
350
485
 
351
- self.bash_session = None
486
+ def device(state: ReplState):
487
+ return DeviceExecHandler(state)
@@ -0,0 +1,62 @@
1
+ import asyncio
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ from copy import copy
4
+ import inspect
5
+ import threading
6
+ import time
7
+ import traceback
8
+
9
+ from adam.utils import log2, log_timing
10
+
11
+ class AsyncExecutor:
12
+ # some lib does not handle asyncio loop properly, as sync exec submit does not work, use another async loop
13
+
14
+ lock = threading.Lock()
15
+ in_queue = set()
16
+ processed: dict[str, float] = {}
17
+ first_processed_at: float = None
18
+ last_processed_at: float = None
19
+
20
+ loop: asyncio.AbstractEventLoop = None
21
+ async_exec: ThreadPoolExecutor = None
22
+
23
+ def preload(action: callable, log_key: str = None):
24
+ with AsyncExecutor.lock:
25
+ if not AsyncExecutor.loop:
26
+ AsyncExecutor.loop = asyncio.new_event_loop()
27
+ AsyncExecutor.async_exec = ThreadPoolExecutor(max_workers=6, thread_name_prefix='async')
28
+ AsyncExecutor.loop.set_default_executor(AsyncExecutor.async_exec)
29
+
30
+ async def a():
31
+ try:
32
+ t0 = time.time()
33
+
34
+ arg_needed = len(action.__code__.co_varnames)
35
+
36
+ if log_key:
37
+ with log_timing(log_key):
38
+ r = action(None) if arg_needed else action()
39
+ else:
40
+ r = action(None) if arg_needed else action()
41
+ if inspect.isawaitable(r):
42
+ await r
43
+
44
+ AsyncExecutor.in_queue.remove(log_key)
45
+ if log_key not in AsyncExecutor.processed:
46
+ AsyncExecutor.processed[log_key] = time.time() - t0
47
+ AsyncExecutor.last_processed_at = time.time()
48
+ except Exception as e:
49
+ log2('preloading error', e, inspect.getsourcelines(action)[0][0])
50
+ traceback.print_exc()
51
+
52
+ if log_key not in AsyncExecutor.in_queue:
53
+ AsyncExecutor.in_queue.add(log_key)
54
+ AsyncExecutor.async_exec.submit(lambda: AsyncExecutor.loop.run_until_complete(a()))
55
+
56
+ def entries_in_queue():
57
+ # no locking
58
+ return copy(AsyncExecutor.in_queue), copy(AsyncExecutor.processed), AsyncExecutor.first_processed_at, AsyncExecutor.last_processed_at
59
+
60
+ def reset():
61
+ AsyncExecutor.first_processed_at = time.time()
62
+ AsyncExecutor.processed.clear()
@@ -0,0 +1,286 @@
1
+ import functools
2
+ import os
3
+ from typing import Iterable
4
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, NestedCompleter, WordCompleter
5
+ from prompt_toolkit.document import Document
6
+
7
+ from adam.config import Config
8
+ from adam.sql.async_executor import AsyncExecutor
9
+ from adam.sql.lark_parser import LarkParser
10
+ from adam.utils import debug, log_timing, offload
11
+ from adam.utils_repl.appendable_completer import AppendableCompleter
12
+ from adam.utils_repl.repl_completer import merge_completions
13
+
14
+ __all__ = [
15
+ "LarkCompleter",
16
+ ]
17
+
18
+ def default_columns(x: list[str]):
19
+ return 'id,x.,y.,z.'.split(',')
20
+
21
+ class LarkCompleter(Completer, AppendableCompleter):
22
+ SYSTEM = 'system'
23
+
24
+ def __init__(self,
25
+ dml: str = None,
26
+ expandables: dict = {},
27
+ variant: str = 'c',
28
+
29
+ name: str = None,
30
+ options_lambda: callable = None,
31
+ auto: str = 'lazy',
32
+ debug = False
33
+ ) -> None:
34
+ self.nested: NestedCompleter = None
35
+ self.options_lambda = options_lambda
36
+ if options_lambda and auto == 'lazy':
37
+ AsyncExecutor.preload(options_lambda, log_key=name)
38
+
39
+ self.variant = variant
40
+ self.parser = None
41
+ self.dml = dml
42
+ self.expandables = expandables
43
+
44
+ self.display_dict = {}
45
+ self.meta_dict = {}
46
+ self.WORD = None
47
+ self.sentence = False
48
+ self.match_middle = False
49
+ self.pattern = None
50
+
51
+ self.debug = debug
52
+
53
+ if variant:
54
+ self.parser = LarkCompleter.lark_parser(variant)
55
+ self.preload_lazy_auto_completes()
56
+
57
+ def __repr__(self):
58
+ return f"LarkCompleter.{self.variant}"
59
+
60
+ def preload_lazy_auto_completes(self):
61
+ for key, value in self.expandables.items():
62
+ if callable(value):
63
+ if self.auto_complete(key) == 'lazy':
64
+ AsyncExecutor.preload(value, log_key=key)
65
+
66
+ def from_lambda(name: str, options_lambda: callable, auto: str = 'lazy'):
67
+ return LarkCompleter(name=name, options_lambda=options_lambda, auto=auto, variant=None)
68
+
69
+ @functools.lru_cache()
70
+ def lark_parser(variant: str):
71
+ dir_path = os.path.dirname(os.path.realpath(__file__))
72
+ with open(dir_path + f"/qingl.lark") as f:
73
+ grammar: str = None
74
+ with log_timing(f'lark.{variant}.file-read'):
75
+ grammar = f.read()
76
+
77
+ common_contexts = {
78
+ 'cd_command.file_name': 'direct-dirs',
79
+ }
80
+
81
+ if variant in ['a', 'c0', 'p0', 'x0']:
82
+ grammar = grammar.replace('start: statement_sequence', f'start: qing_{variant}_statement')
83
+ contexts_by_path = {
84
+ } | common_contexts
85
+
86
+ debug(f'* GRAMMAR replaced to start: qing_{variant}_statement')
87
+
88
+ return LarkParser(grammar, contexts_by_path)
89
+ elif variant == 'system':
90
+ grammar = grammar.replace('start: statement_sequence', f'start: qing_{variant}_statement')
91
+ contexts_by_path = {
92
+ } | common_contexts
93
+
94
+ return LarkParser(grammar, contexts_by_path)
95
+
96
+ grammar = grammar.replace('qing_statement: qing_p_statement', f'qing_statement: qing_{variant}_statement')
97
+ debug(f'* GRAMMAR replaced to qing_statement: qing_{variant}_statement')
98
+
99
+ bash_contexts = {
100
+ 'bash_statement.host_name': 'hosts',
101
+ 'bash_statement.bash_command': 'bash-commands',
102
+ }
103
+
104
+ contexts_by_path = {
105
+ 'describe_keyspace.keyspace_name': 'keyspaces',
106
+ 'keyspace_ref.keyspace_path.namespace_ref.identifier_ref': 'tables',
107
+ 'preview_table_statement.path.identifier_ref': 'tables',
108
+
109
+ 'insert_statement.insert_select': 'column-names',
110
+ 'update_statement.set_clause.path.identifier_ref': 'column-names',
111
+ 'update_statement.where_clause.cond.expr.path.identifier_ref': 'column-names',
112
+ 'delete_statement.where_clause.cond.expr.path.identifier_ref': 'column-names',
113
+
114
+ 'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',
115
+ 'select_from.where_clause.cond.expr.path.identifier_ref': 'columns',
116
+ 'select_from.where_clause.cond.expr.logical_term.and_expr.cond.expr.path.identifier_ref': 'columns',
117
+ 'select_from.group_by_clause.group_term.expr.path.identifier_ref': 'columns',
118
+ 'select_statement.order_by_clause.ordering_term.expr.path.identifier_ref': 'columns',
119
+ 'select_from.from_clause.from_terms.join_clause.ansi_join_clause.ansi_join_predicate.expr.path.identifier_ref': 'columns',
120
+ 'select_from.from_clause.from_terms.join_clause.ansi_join_clause.ansi_join_predicate.expr.comparison_term.relational_expr.expr.path.identifier_ref': 'columns',
121
+
122
+ 'select_from.from_clause.from_terms.from_generic.alias.identifier_ref': 'column-aliases',
123
+
124
+ 'select_statement.limit_clause.expr.literal.nbr.digit': 'limits',
125
+ } | common_contexts
126
+
127
+ if variant == 'p':
128
+ contexts_by_path = bash_contexts | contexts_by_path
129
+ elif variant == 'c':
130
+ contexts_by_path = {
131
+ 'export_table.path.identifier_ref': 'tables',
132
+ 'show_column_counts_command.path.identifier_ref': 'tables',
133
+ 'export_statement.export_tables.keyspace_name': 'keyspaces',
134
+
135
+ 'alter_tables_statement.properties.property.property_name': 'table-props',
136
+ 'alter_cql_table_statement.properties.property.property_name': 'table-props',
137
+ 'alter_tables_statement.properties.property.property_value.literal': 'table-props-value',
138
+ 'alter_cql_table_statement.properties.property.property_value.literal': 'table-props-value',
139
+
140
+ 'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',
141
+ 'export_statement.export_tables.export_table.column_name_list.column_name': 'columns',
142
+
143
+ 'consistency_statement.consistency': 'consistencies',
144
+ 'export_statement.export_to.export_database_type': 'export-database-types',
145
+ 'drop_export_database.export_database_name': 'export-databases',
146
+ 'use_export_db_statement.export_database_name': 'export-databases',
147
+ 'clean_up_export_session_statement.clean_up_export_sessions.export_session_name': 'export-sessions',
148
+ 'show_export_command.export_session_name': 'export-sessions',
149
+ 'import_statement.import_session.export_session_name': 'export-sessions-incomplete',
150
+ 'download_session_statement.export_session_name': 'export-sessions-incomplete',
151
+ } | bash_contexts | contexts_by_path
152
+ elif variant == 'l':
153
+ contexts_by_path = {
154
+ 'add_partition_action.partition_ref.partition_name': 'partition-columns',
155
+ 'show_topn_statement.topn_count': 'topn-counts',
156
+ 'show_topn_statement.topn_type': 'topn-types',
157
+ 'show_topn_statement.topn_window': 'topn-windows'
158
+ } | contexts_by_path
159
+ elif variant == 'x':
160
+ contexts_by_path = {
161
+ 'show_column_counts_command.path.identifier_ref': 'tables',
162
+ 'drop_export_database.export_database_name': 'export-databases',
163
+ 'use_export_db_statement.export_database_name': 'export-databases',
164
+ } | contexts_by_path
165
+
166
+ grammar = grammar.replace('select_clause: "SELECT"i hint_comment? projection', 'select_clause: ("SELECT"i | "XELECT"i) hint_comment? projection')
167
+
168
+ with offload():
169
+ with open('/tmp/grammar.lark', 'wt') as f:
170
+ f.write(grammar)
171
+
172
+ return LarkParser(grammar, contexts_by_path)
173
+
174
+ def get_completions(
175
+ self, document: Document, complete_event: CompleteEvent
176
+ ) -> Iterable[Completion]:
177
+ if not self.nested and self.options_lambda:
178
+ # for lazy completions
179
+ self.nested = NestedCompleter.from_nested_dict(self.options_lambda())
180
+
181
+ nested_words = set()
182
+
183
+ if self.nested:
184
+ # from NestedCompleter
185
+
186
+ # Split document.
187
+ text = document.text_before_cursor.lstrip()
188
+ stripped_len = len(document.text_before_cursor) - len(text)
189
+
190
+ # If there is a space, check for the first term, and use a
191
+ # subcompleter.
192
+ if " " in text:
193
+ first_term = text.split()[0]
194
+ completer = self.nested.options.get(first_term)
195
+
196
+ # If we have a sub completer, use this for the completions.
197
+ if completer is not None:
198
+ remaining_text = text[len(first_term) :].lstrip()
199
+ move_cursor = len(text) - len(remaining_text) + stripped_len
200
+
201
+ new_document = Document(
202
+ remaining_text,
203
+ cursor_position=document.cursor_position - move_cursor,
204
+ )
205
+
206
+ for c in completer.get_completions(new_document, complete_event):
207
+ nested_words.add(c.text)
208
+ yield c
209
+
210
+ # No space in the input: behave exactly like `WordCompleter`.
211
+ else:
212
+ completer = WordCompleter(
213
+ list(self.nested.options.keys()), ignore_case=self.nested.ignore_case
214
+ )
215
+ for c in completer.get_completions(document, complete_event):
216
+ nested_words.add(c.text)
217
+ yield c
218
+
219
+ if self.parser:
220
+ full = document.text_before_cursor
221
+ if self.dml:
222
+ full = self.dml + ' ' + full
223
+
224
+ words0 = []
225
+ words1 = []
226
+ context = {}
227
+ for word in self.parser.next_terminals(full, context=context):
228
+ if ex := self.expandable(word):
229
+ if ex in self.expandables:
230
+ e = self.expandables[ex]
231
+ if callable(e):
232
+ if self.auto_complete(ex) != 'off':
233
+ ctx = None
234
+ if 'last-id' in context:
235
+ ctx = context['last-id']
236
+ e = e(ctx)
237
+ words0.extend(e)
238
+ else:
239
+ words0.extend(e)
240
+ else:
241
+ words1.append(word)
242
+ words = words0 + words1
243
+
244
+ word_before_cursor = document.get_word_before_cursor(
245
+ WORD=self.WORD, pattern=self.pattern
246
+ )
247
+
248
+ word_before_cursor = word_before_cursor.lower()
249
+
250
+ def word_matches(word: str) -> bool:
251
+ return word.lower().startswith(word_before_cursor)
252
+
253
+ for word in words:
254
+ if word_matches(word) and word not in nested_words:
255
+ display = self.display_dict.get(word, word)
256
+ display_meta = self.meta_dict.get(word, "")
257
+ yield Completion(
258
+ word,
259
+ -len(word_before_cursor),
260
+ display=display,
261
+ display_meta=display_meta,
262
+ )
263
+
264
+ def completions_for_nesting(self, dml: str = None):
265
+ if dml:
266
+ return {dml: LarkCompleter(dml, expandables=self.expandables, variant=self.variant)}
267
+
268
+ return {
269
+ word.text.lower(): LarkCompleter(word.text, expandables=self.expandables, variant=self.variant)
270
+ for word in self.get_completions(Document(''), None)
271
+ }
272
+
273
+ def expandable(self, word: str):
274
+ return word.strip('`') if word.startswith('`') else None
275
+
276
+ def append_completions(self, key: str, value: dict[str, any]):
277
+ if isinstance(value, LarkCompleter) and self.variant == value.variant:
278
+ return
279
+
280
+ if self.nested:
281
+ self.nested = NestedCompleter.from_nested_dict(merge_completions(self.nested.options, value))
282
+ else:
283
+ self.nested = NestedCompleter.from_nested_dict(value)
284
+
285
+ def auto_complete(self, key: str, default = 'lazy'):
286
+ return Config().get(f'auto-complete.{self.variant}.{key}', default=default)