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
@@ -1,7 +1,7 @@
1
+ from adam.commands import extract_options, extract_sequence, extract_trailing_options
1
2
  from adam.commands.command import Command
2
- from adam.commands.commands_utils import show_table
3
+ from adam.commands.cql.utils_cql import cassandra
3
4
  from adam.config import Config
4
- from adam.k8s_utils.statefulsets import StatefulSets
5
5
  from adam.repl_state import ReplState, RequiredState
6
6
 
7
7
  class ShowProcesses(Command):
@@ -26,28 +26,25 @@ class ShowProcesses(Command):
26
26
  if not(args := self.args(cmd)):
27
27
  return super().run(cmd, state)
28
28
 
29
- state, args = self.apply_state(args, state)
30
- if not self.validate_state(state):
31
- return state
29
+ with self.validate(args, state) as (args, state):
30
+ with extract_trailing_options(args, '&') as (args, backgrounded):
31
+ with extract_options(args, ['-s', '--show']) as (args, show_out):
32
+ with extract_sequence(args, ['with', 'recipe', '=', 'qing']) as (_, recipe_qing):
33
+ cols = Config().get('processes.columns', 'pod,cpu-metrics,mem')
34
+ header = Config().get('processes.header', 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT')
35
+ if recipe_qing:
36
+ cols = Config().get('processes-qing.columns', 'pod,cpu,mem')
37
+ header = Config().get('processes-qing.header', 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT')
32
38
 
33
- args, show_output = Command.extract_options(args, ['-s', '--show'])
39
+ with cassandra(state) as pods:
40
+ pods.display_table(cols, header, show_out=show_out, backgrounded=backgrounded)
34
41
 
35
- cols = Config().get('processes.columns', 'pod,cpu,mem')
36
- header = Config().get('processes.header', 'POD_NAME,CPU,MEM/LIMIT')
37
-
38
- if state.pod:
39
- show_table(state, [state.pod], cols, header, show_output=show_output)
40
- elif state.sts:
41
- pod_names = [pod.metadata.name for pod in StatefulSets.pods(state.sts, state.namespace)]
42
- show_table(state, pod_names, cols, header, show_output=show_output)
43
-
44
- return state
42
+ return state
45
43
 
46
44
  def completion(self, state: ReplState):
47
- if not state.sts:
48
- return {}
49
-
50
- return super().completion(state)
45
+ recipes = ['metrics', 'qing']
46
+ return super().completion(state, {'with': {'recipe': {'=': {r: {'-s': {'&': None}, '&': None} for r in recipes}}}, '-s': {'&': None}, '&': None})
47
+ # return super().completion(state, {'with': {'recipe': {'=': {'metrics': {'-s': {'&': None}, '&': None}, 'qing': {'-s': {'&': None}}}}}, '-s': {'&': None}, '&': None})
51
48
 
52
49
  def help(self, _: ReplState):
53
- return f'{ShowProcesses.COMMAND} [-s]\t show process overview -s show commands on nodes'
50
+ return f'{ShowProcesses.COMMAND} [with recipe qing|metrics] [-s]\t show process overview -s show commands on nodes'
@@ -1,7 +1,7 @@
1
+ from adam.commands import extract_options, extract_trailing_options
1
2
  from adam.commands.command import Command
2
- from adam.commands.commands_utils import show_table
3
+ from adam.commands.cql.utils_cql import cassandra
3
4
  from adam.config import Config
4
- from adam.k8s_utils.statefulsets import StatefulSets
5
5
  from adam.repl_state import ReplState, RequiredState
6
6
 
7
7
  class ShowStorage(Command):
@@ -26,27 +26,18 @@ class ShowStorage(Command):
26
26
  if not(args := self.args(cmd)):
27
27
  return super().run(cmd, state)
28
28
 
29
- state, args = self.apply_state(args, state)
30
- if not self.validate_state(state):
31
- return state
29
+ with self.validate(args, state) as (args, state):
30
+ with extract_trailing_options(args, '&') as (args, backgrounded):
31
+ with extract_options(args, ['-s', '--show']) as (args, show_out):
32
+ cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
33
+ header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
34
+ with cassandra(state) as pods:
35
+ pods.display_table(cols, header, show_out=show_out, backgrounded=backgrounded)
32
36
 
33
- args, show_output = Command.extract_options(args, ['-s', '--show'])
34
-
35
- cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
36
- header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
37
- if state.pod:
38
- show_table(state, [state.pod], cols, header, show_output=show_output)
39
- elif state.sts:
40
- pod_names = [pod.metadata.name for pod in StatefulSets.pods(state.sts, state.namespace)]
41
- show_table(state, pod_names, cols, header, show_output=show_output)
42
-
43
- return state
37
+ return state
44
38
 
45
39
  def completion(self, state: ReplState):
46
- if not state.sts:
47
- return {}
48
-
49
- return super().completion(state)
40
+ return super().completion(state, {'-s': {'&': None}, '&': None})
50
41
 
51
42
  def help(self, _: ReplState):
52
43
  return f'{ShowStorage.COMMAND} [-s]\t show storage overview -s show commands on nodes'
adam/commands/watch.py CHANGED
@@ -6,12 +6,12 @@ from typing import List
6
6
  from adam.commands.command import Command
7
7
  from adam.commands.commands_utils import show_pods, show_rollout
8
8
  from adam.config import Config
9
- from adam.k8s_utils.statefulsets import StatefulSets
9
+ from adam.utils_k8s.statefulsets import StatefulSets
10
10
  from adam.repl_state import ReplState, RequiredState
11
- from adam.utils import convert_seconds, log2
11
+ from adam.utils import log2
12
12
 
13
13
  class Watch(Command):
14
- COMMAND = 'watch'
14
+ COMMAND = 'watch cassandra pods'
15
15
 
16
16
  # the singleton pattern
17
17
  def __new__(cls, *args, **kwargs):
@@ -26,37 +26,34 @@ class Watch(Command):
26
26
  return Watch.COMMAND
27
27
 
28
28
  def required(self):
29
- return RequiredState.CLUSTER_OR_POD
29
+ return RequiredState.NAMESPACE
30
30
 
31
31
  def run(self, cmd: str, state: ReplState):
32
32
  if not(args := self.args(cmd)):
33
33
  return super().run(cmd, state)
34
34
 
35
- state, args = self.apply_state(args, state)
36
- if not self.validate_state(state):
37
- return state
38
-
39
- pods = StatefulSets.pods(state.sts, state.namespace)
40
- if not pods:
41
- log2("No pods are found.")
42
- return state
35
+ with self.validate(args, state) as (args, state):
36
+ pods = StatefulSets.pods(state.sts, state.namespace)
37
+ if not pods:
38
+ log2("No pods are found.")
39
+ return state
43
40
 
44
- stop_event = threading.Event()
45
- thread = threading.Thread(target=self.loop, args=(stop_event, state.sts, pods, state.namespace), daemon=True)
46
- thread.start()
41
+ stop_event = threading.Event()
42
+ thread = threading.Thread(target=self.loop, args=(stop_event, state.sts, pods, state.namespace), daemon=True)
43
+ thread.start()
47
44
 
48
- try:
49
- log2(f"Press Ctrl+C to break.")
45
+ try:
46
+ log2(f"Press Ctrl+C to break.")
50
47
 
51
- time.sleep(Config().get('watch.timeout', 3600 * 1))
52
- except KeyboardInterrupt:
53
- pass
48
+ time.sleep(Config().get('watch.timeout', 3600 * 1))
49
+ except KeyboardInterrupt:
50
+ pass
54
51
 
55
- log2("Stopping watch...")
56
- stop_event.set()
57
- thread.join()
52
+ log2("Stopping watch...")
53
+ stop_event.set()
54
+ thread.join()
58
55
 
59
- return state
56
+ return state
60
57
 
61
58
  def loop(self, stop_flag: threading.Event, sts: str, pods: List[client.V1Pod], ns: str):
62
59
  show_pods(pods, ns)
@@ -73,13 +70,13 @@ class Watch(Command):
73
70
  cnt = Config().get('watch.interval', 10)
74
71
 
75
72
  def completion(self, state: ReplState):
76
- if state.pod:
77
- return {}
73
+ if sc := super().completion(state):
74
+ if state.sts:
75
+ return sc
78
76
 
79
- if not state.sts:
80
- return {Watch.COMMAND: {n: None for n in StatefulSets.list_sts_names()}}
77
+ return super().completion(state, {n: None for n in StatefulSets.list_sts_names()})
81
78
 
82
- return {Watch.COMMAND: None}
79
+ return {}
83
80
 
84
81
  def help(self, _: ReplState):
85
- return f'{Watch.COMMAND}\t watch pod changes'
82
+ return f'{Watch.COMMAND}\t watch Cassandra pod changes'
adam/config.py CHANGED
@@ -1,14 +1,19 @@
1
+ import os
1
2
  from typing import TypeVar, cast
2
3
  import yaml
3
4
 
4
5
  from . import __version__
5
- from adam.utils import copy_config_file, get_deep_keys, log2
6
+ from adam.utils import LogConfig, copy_config_file, get_deep_keys, log2
6
7
 
7
8
  T = TypeVar('T')
8
9
 
9
10
  class Config:
10
11
  EMBEDDED_PARAMS = {}
11
12
 
13
+ LogConfig.is_debug = lambda: Config().is_debug()
14
+ LogConfig.is_debug_complete = lambda: Config().get('debugs.complete', False)
15
+ LogConfig.is_debug_timing = lambda: Config().get('debugs.timings', False)
16
+
12
17
  # the singleton pattern
13
18
  def __new__(cls, *args, **kwargs):
14
19
  if not hasattr(cls, 'instance'): cls.instance = super(Config, cls).__new__(cls)
@@ -17,6 +22,7 @@ class Config:
17
22
 
18
23
  def __init__(self, path: str = None, is_user_entry = False):
19
24
  if path:
25
+ self.wait_log_flag = False
20
26
  try:
21
27
  with open(path) as f:
22
28
  self.params = cast(dict[str, any], yaml.safe_load(f))
@@ -37,11 +43,7 @@ class Config:
37
43
  return get_deep_keys(self.params)
38
44
 
39
45
  def is_debug(self):
40
- return Config().get('debug.show-out', False)
41
-
42
- def debug(self, s: None):
43
- if self.is_debug():
44
- log2(f'DEBUG {s}')
46
+ return os.getenv('QING_DEV', 'false').lower() == 'true' or Config().get('debug', False)
45
47
 
46
48
  def get(self, key: str, default: T) -> T:
47
49
  # params['nodetool']['status']['max-nodes']
adam/embedded_params.py CHANGED
@@ -1,2 +1,2 @@
1
1
  def config():
2
- return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'medusa': {'restore-auto-complete': False}, 'nodetool': {'workers': 32, 'samples': 3, 'commands_in_line': 40}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,CPU,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.13', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'a', 'auto-enter-app': 'c3/c3', 'auto-enter-only-cluster': True}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'debug': {'timings': False, 'exit-on-error': False, 'show-parallelism': False, 'show-out': False}}
2
+ return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}, 'queries': {'last10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY ts DESC LIMIT {limit}", 'slow10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY CAST(duration AS REAL) DESC LIMIT {limit}", 'top10': "SELECT min(c) AS cluster, line, COUNT(*) AS cnt, avg(CAST(duration AS REAL)) AS duration\nFROM audit WHERE drive <> 'z' and ({date_condition})\nGROUP BY line ORDER BY cnt DESC LIMIT {limit}"}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'download': {'workers': 8}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}, 'log-prefix': '/tmp/qing'}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'local-tmp-dir': '/tmp/qing-db', 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'nodetool': {'workers': 96, 'commands_in_line': 40, 'status': {'workers': 32, 'samples': 3, 'commands_in_line': 40}}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-log-file': True, 'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'auto-complete': {'c': {'tables': 'lazy'}, 'x': {'tables': 'lazy'}, 'cli': {'cp': 'jit'}, 'export': {'databases': 'jit'}, 'medusa': {'backups': 'jit'}, 'reaper': {'schedules': 'lazy'}}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
adam/log.py CHANGED
@@ -4,6 +4,8 @@ import os
4
4
  import sys
5
5
  import click
6
6
 
7
+ from adam.utils import log_exc
8
+
7
9
  class Log:
8
10
  DEBUG = False
9
11
 
@@ -28,7 +30,7 @@ class Log:
28
30
  print(file=sys.stderr)
29
31
 
30
32
  def log_to_file(config: dict[any, any]):
31
- try:
33
+ with log_exc():
32
34
  base = f"/tmp/logs"
33
35
  os.makedirs(base, exist_ok=True)
34
36
 
@@ -42,6 +44,4 @@ class Log:
42
44
  except:
43
45
  f.write(config)
44
46
  else:
45
- f.write(config)
46
- except:
47
- pass
47
+ f.write(config)
adam/pod_exec_result.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import yaml
2
2
 
3
+ from adam.utils import log_exc
4
+
3
5
  class PodExecResult:
4
6
  # {
5
7
  # 'metadata': {},
@@ -15,19 +17,25 @@ class PodExecResult:
15
17
  # ]
16
18
  # }
17
19
  # }
18
- def __init__(self, stdout: str, stderr: str, command: str = None, error_output: str = None):
20
+ def __init__(self, stdout: str, stderr: str, command: str = None, error_output: str = None, pod: str = None, log_file: str = None):
19
21
  self.stdout: str = stdout
20
22
  self.stderr: str = stderr
21
23
  self.command: str = command
22
24
  if error_output:
23
25
  self.error = yaml.safe_load(error_output)
26
+ self.pod = pod
27
+ self.log_file = log_file
24
28
 
25
29
  def exit_code(self) -> int:
26
30
  code = 0
27
31
 
28
- try:
32
+ with log_exc(False):
29
33
  code = self.error['details']['causes'][0]['message']
30
- except:
31
- pass
32
34
 
33
- return code
35
+ return code
36
+
37
+ def __str__(self):
38
+ return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
39
+
40
+ def __audit_extra__(self):
41
+ return self.log_file if self.log_file else None
adam/repl.py CHANGED
@@ -1,27 +1,31 @@
1
1
  import os
2
- import re
3
2
  import time
4
- import traceback
3
+ from typing import cast
5
4
  import click
6
- from prompt_toolkit.completion import NestedCompleter
7
5
  from prompt_toolkit.key_binding import KeyBindings
8
6
 
9
7
  from adam.cli_group import cli
10
- from adam.commands.command import Command
8
+ from adam.commands.command import Command, InvalidArgumentsException, InvalidStateException
11
9
  from adam.commands.command_helpers import ClusterCommandHelper
10
+ from adam.commands.devices.devices import Devices
12
11
  from adam.commands.help import Help
13
- from adam.commands.postgres.postgres_session import PostgresSession
14
12
  from adam.config import Config
15
- from adam.k8s_utils.kube_context import KubeContext
16
- from adam.k8s_utils.statefulsets import StatefulSets
13
+ from adam.utils_audits import Audits
14
+ from adam.utils_k8s.kube_context import KubeContext
17
15
  from adam.log import Log
18
16
  from adam.repl_commands import ReplCommands
19
17
  from adam.repl_session import ReplSession
20
18
  from adam.repl_state import ReplState
21
- from adam.utils import deep_merge_dicts, lines_to_tabular, log2
19
+ from adam.utils import clear_wait_log_flag, debug_trace, deep_sort_dict, tabulize, log2, log_exc, log_timing
22
20
  from adam.apps import Apps
21
+ from adam.utils_repl.repl_completer import ReplCompleter, merge_completions
23
22
  from . import __version__
24
23
 
24
+ import nest_asyncio
25
+ nest_asyncio.apply()
26
+
27
+ import asyncio
28
+
25
29
  def enter_repl(state: ReplState):
26
30
  if os.getenv('QING_DROPPED', 'false') == 'true':
27
31
  log2('You have dropped to bash from another qing instance. Please enter "exit" to go back to qing.')
@@ -33,54 +37,13 @@ def enter_repl(state: ReplState):
33
37
  session = ReplSession().prompt_session
34
38
 
35
39
  def prompt_msg():
36
- msg = ''
37
- if state.device == ReplState.P:
38
- msg = f'{ReplState.P}:'
39
- pg = PostgresSession(state.namespace, state.pg_path) if state.pg_path else None
40
- if pg and pg.db:
41
- msg += pg.db
42
- elif pg and pg.host:
43
- msg += pg.host
44
- elif state.device == ReplState.A:
45
- msg = f'{ReplState.A}:'
46
- if state.app_env:
47
- msg += state.app_env
48
- if state.app_app:
49
- msg += f'/{state.app_app}'
50
- else:
51
- msg = f'{ReplState.C}:'
52
- if state.pod:
53
- # cs-d0767a536f-cs-d0767a536f-default-sts-0
54
- group = re.match(r".*?-.*?-(.*)", state.pod)
55
- msg += group[1]
56
- elif state.sts:
57
- # cs-d0767a536f-cs-d0767a536f-default-sts
58
- group = re.match(r".*?-.*?-(.*)", state.sts)
59
- msg += group[1]
40
+ msg = state.__str__()
60
41
 
61
42
  return f"{msg}$ " if state.bash_session else f"{msg}> "
62
43
 
63
44
  Log.log2(f'kaqing {__version__}')
64
- ss = StatefulSets.list_sts_name_and_ns()
65
-
66
- if state.device == ReplState.C:
67
- if not ss:
68
- raise Exception("no Cassandra clusters found")
69
- elif len(ss) == 1 and Config().get('repl.auto-enter-only-cluster', True):
70
- cluster = ss[0]
71
- state.sts = cluster[0]
72
- state.namespace = cluster[1]
73
- state.wait_log(f'Moving to the only Cassandra cluster: {state.sts}@{state.namespace}...')
74
- elif state.device == ReplState.A:
75
- if app := Config().get('repl.auto-enter-app', 'c3/c3'):
76
- if app != 'no':
77
- ea = app.split('/')
78
- state.app_env = ea[0]
79
- if len(ea) > 1:
80
- state.app_app = ea[1]
81
- state.wait_log(f'Moving to {state.app_env}/{state.app_app}...')
82
- else:
83
- state.wait_log(f'Moving to {state.app_env}...')
45
+
46
+ Devices.device(state).enter(state)
84
47
 
85
48
  kb = KeyBindings()
86
49
 
@@ -88,72 +51,122 @@ def enter_repl(state: ReplState):
88
51
  def _(event):
89
52
  event.app.current_buffer.text = ''
90
53
 
91
- # use sorted command list only for auto-completion
92
- sorted_cmds = sorted(cmd_list, key=lambda cmd: cmd.command())
93
- while True:
94
- try:
95
- completer = NestedCompleter.from_nested_dict({})
96
- if not state.bash_session:
97
- completions = {}
98
- # app commands are available only on a: drive
99
- if state.device == ReplState.A and state.app_app:
100
- completions = Apps(path='apps.yaml').commands()
101
-
102
- for cmd in sorted_cmds:
103
- s1 = time.time()
104
- try:
105
- completions = deep_merge_dicts(completions, cmd.completion(state))
106
- finally:
107
- if Config().get('debug.timings', False):
108
- Config().debug('Timing completion calc', cmd.command(), f'{time.time() - s1:.2f}')
109
-
110
- completer = NestedCompleter.from_nested_dict(completions)
111
-
112
- cmd = session.prompt(prompt_msg(), completer=completer, key_bindings=kb)
113
- s0 = time.time()
114
-
115
- if state.bash_session:
116
- if cmd.strip(' ') == 'exit':
117
- state.exit_bash()
118
- continue
119
-
120
- cmd = f'bash {cmd}'
121
-
122
- if cmd and cmd.strip(' ') and not cmds.run(cmd, state):
123
- c_sql_tried = False
124
- if state.device == ReplState.P:
125
- pg = PostgresSession(state.namespace, state.pg_path)
126
- if pg.db:
127
- c_sql_tried = True
128
- cmd = f'pg {cmd}'
129
- cmds.run(cmd, state)
130
- elif state.device == ReplState.A:
131
- if state.app_app:
132
- c_sql_tried = True
133
- cmd = f'app {cmd}'
134
- cmds.run(cmd, state)
135
- elif state.sts:
136
- c_sql_tried = True
137
- cmd = f'cql {cmd}'
138
- cmds.run(cmd, state)
139
-
140
- if not c_sql_tried:
141
- log2(f'* Invalid command: {cmd}')
142
- log2()
143
- lines = [c.help(state) for c in cmd_list if c.help(state)]
144
- log2(lines_to_tabular(lines, separator='\t'))
145
- except EOFError: # Handle Ctrl+D (EOF) for graceful exit
146
- break
147
- except Exception as e:
148
- if Config().get('debug.exit-on-error', False):
149
- raise e
150
- else:
151
- log2(e)
152
- Config().debug(traceback.format_exc())
153
- finally:
154
- state.clear_wait_log_flag()
155
- if Config().get('debug.timings', False) and 'cmd' in locals() and 's0' in locals():
156
- print('Timing command', cmd, f'{time.time() - s0:.2f}')
54
+ with Audits.offload() as exec:
55
+ # warm up AWS lambda - this log line may timeout and get lost, which is fine
56
+ exec.submit(Audits.log, 'entering kaqing repl', state.namespace, 'z', 0.0)
57
+
58
+ s0 = time.time()
59
+
60
+ # use sorted command list only for auto-completion
61
+ sorted_cmds = sorted(cmd_list, key=lambda cmd: cmd.command())
62
+ while True:
63
+ cmd: str = None
64
+ result = None
65
+ try:
66
+ completer = ReplCompleter.from_nested_dict({})
67
+ if not state.bash_session:
68
+ with log_timing('completion-calcs'):
69
+ completions = {}
70
+ # app commands are available only on a: drive
71
+ if state.device == ReplState.A and state.app_app:
72
+ completions = log_timing('actions', lambda: Apps(path='apps.yaml').commands())
73
+
74
+ for c in sorted_cmds:
75
+ with log_exc(f'* {c.command()} command returned None completions.'):
76
+ completions = log_timing(c.command(), lambda: deep_sort_dict(merge_completions(completions, c.completion(state))))
77
+
78
+ # print(json.dumps(completions, indent=4))
79
+ completer = ReplCompleter.from_nested_dict(completions)
80
+
81
+ cmd = session.prompt(prompt_msg(), completer=completer, key_bindings=kb)
82
+ s0 = time.time()
83
+
84
+ if state.bash_session:
85
+ if cmd.strip(' ') == 'exit':
86
+ state.exit_bash()
87
+ continue
88
+
89
+ cmd = f'bash {cmd}'
90
+
91
+ def targetted(state: ReplState, cmd: str):
92
+ if not (cmd.startswith('@') and len(arry := cmd.split(' ')) > 1):
93
+ return state, cmd
94
+
95
+ if state.device == ReplState.A and state.app_app or state.device == ReplState.P:
96
+ state.push(pod_targetted=True)
97
+
98
+ state.app_pod = arry[0].strip('@')
99
+ cmd = ' '.join(arry[1:])
100
+ elif state.device == ReplState.P:
101
+ state.push(pod_targetted=True)
102
+
103
+ state.app_pod = arry[0].strip('@')
104
+ cmd = ' '.join(arry[1:])
105
+ elif state.sts:
106
+ state.push(pod_targetted=True)
107
+
108
+ state.pod = arry[0].strip('@')
109
+ cmd = ' '.join(arry[1:])
110
+
111
+ return (state, cmd)
112
+
113
+ target, cmd = targetted(state, cmd)
114
+ try:
115
+ if cmd and cmd.strip(' ') and not (result := cmds.run(cmd, target)):
116
+ result = try_device_default_action(target, cmds, cmd_list, cmd)
117
+ except InvalidStateException:
118
+ pass
119
+ except InvalidArgumentsException:
120
+ pass
121
+
122
+ if result and type(result) is ReplState and (s := cast(ReplState, result).export_session) != state.export_session:
123
+ state.export_session = s
124
+
125
+ except EOFError: # Handle Ctrl+D (EOF) for graceful exit
126
+ break
127
+ except Exception as e:
128
+ if Config().get('debugs.exit-on-error', False):
129
+ raise e
130
+ else:
131
+ log2(e)
132
+ debug_trace()
133
+ finally:
134
+ if not state.bash_session:
135
+ state.pop()
136
+
137
+ clear_wait_log_flag()
138
+ if cmd:
139
+ log_timing(f'command {cmd}', s0=s0)
140
+
141
+ # offload audit logging
142
+ if cmd and (state.device != ReplState.L or Config().get('audit.log-audit-queries', False)):
143
+ exec.submit(Audits.log, cmd, state.namespace, state.device, time.time() - s0, get_audit_extra(result))
144
+
145
+ def try_device_default_action(state: ReplState, cmds: Command, cmd_list: list[Command], cmd: str):
146
+ action_taken, result = Devices.device(state).try_fallback_action(cmds, state, cmd)
147
+
148
+ if not action_taken:
149
+ log2(f'* Invalid command: {cmd}')
150
+ log2()
151
+ tabulize([c.help(state) for c in cmd_list if c.help(state)], separator='\t', to=2)
152
+
153
+ return result
154
+
155
+ def get_audit_extra(result: any):
156
+ if not result:
157
+ return None
158
+
159
+ if type(result) is list:
160
+ extras = set()
161
+
162
+ for r in result:
163
+ if hasattr(r, '__audit_extra__') and (x := r.__audit_extra__()):
164
+ extras.add(x)
165
+
166
+ return ','.join(list(extras))
167
+
168
+ if hasattr(result, '__audit_extra__') and (x := result.__audit_extra__()):
169
+ return x
157
170
 
158
171
  @cli.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=ClusterCommandHelper, help="Enter interactive shell.")
159
172
  @click.option('--kubeconfig', '-k', required=False, metavar='path', help='path to kubeconfig file')
@@ -167,6 +180,9 @@ def repl(kubeconfig: str, config: str, param: list[str], cluster:str, namespace:
167
180
  if not KubeContext.init_params(config, param):
168
181
  return
169
182
 
170
- state = ReplState(device=Config().get('repl.start-drive', 'a'), ns_sts=cluster, namespace=namespace, in_repl=True)
171
- state, _ = state.apply_args(extra_args)
183
+ state = ReplState(ns_sts=cluster, namespace=namespace, in_repl=True)
184
+ state, _ = state.apply_device_arg(extra_args)
185
+ if not state.device:
186
+ state.device=Config().get('repl.start-drive', 'a')
187
+
172
188
  enter_repl(state)