kaqing 2.0.52__py3-none-any.whl → 2.0.184__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 (223) 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 +15 -19
  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 +37 -63
  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 +98 -0
  25. adam/commands/audit/audit.py +81 -0
  26. adam/commands/audit/audit_repair_tables.py +72 -0
  27. adam/commands/audit/audit_run.py +50 -0
  28. adam/commands/audit/show_last10.py +37 -0
  29. adam/commands/audit/show_slow10.py +36 -0
  30. adam/commands/audit/show_top10.py +36 -0
  31. adam/commands/audit/utils_show_top10.py +71 -0
  32. adam/commands/bash/__init__.py +5 -0
  33. adam/commands/bash/bash.py +36 -0
  34. adam/commands/bash/bash_completer.py +93 -0
  35. adam/commands/bash/utils_bash.py +16 -0
  36. adam/commands/cat.py +36 -0
  37. adam/commands/cd.py +14 -89
  38. adam/commands/check.py +18 -21
  39. adam/commands/cli_commands.py +5 -6
  40. adam/commands/clipboard_copy.py +86 -0
  41. adam/commands/code.py +57 -0
  42. adam/commands/command.py +197 -35
  43. adam/commands/commands_utils.py +15 -31
  44. adam/commands/cql/cql_completions.py +29 -8
  45. adam/commands/cql/cqlsh.py +12 -27
  46. adam/commands/cql/utils_cql.py +297 -0
  47. adam/commands/deploy/code_start.py +7 -10
  48. adam/commands/deploy/code_stop.py +4 -21
  49. adam/commands/deploy/code_utils.py +5 -5
  50. adam/commands/deploy/deploy.py +4 -21
  51. adam/commands/deploy/deploy_frontend.py +14 -17
  52. adam/commands/deploy/deploy_pg_agent.py +3 -6
  53. adam/commands/deploy/deploy_pod.py +71 -79
  54. adam/commands/deploy/deploy_utils.py +16 -26
  55. adam/commands/deploy/undeploy.py +4 -21
  56. adam/commands/deploy/undeploy_frontend.py +4 -7
  57. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  58. adam/commands/deploy/undeploy_pod.py +15 -16
  59. adam/commands/devices/__init__.py +0 -0
  60. adam/commands/devices/device.py +123 -0
  61. adam/commands/devices/device_app.py +163 -0
  62. adam/commands/devices/device_auit_log.py +49 -0
  63. adam/commands/devices/device_cass.py +179 -0
  64. adam/commands/devices/device_export.py +84 -0
  65. adam/commands/devices/device_postgres.py +150 -0
  66. adam/commands/devices/devices.py +25 -0
  67. adam/commands/download_file.py +47 -0
  68. adam/commands/exit.py +1 -4
  69. adam/commands/export/__init__.py +0 -0
  70. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  71. adam/commands/export/clean_up_export_sessions.py +39 -0
  72. adam/commands/export/download_export_session.py +39 -0
  73. adam/commands/export/drop_export_database.py +39 -0
  74. adam/commands/export/drop_export_databases.py +37 -0
  75. adam/commands/export/export.py +53 -0
  76. adam/commands/export/export_databases.py +245 -0
  77. adam/commands/export/export_select.py +59 -0
  78. adam/commands/export/export_select_x.py +54 -0
  79. adam/commands/export/export_sessions.py +209 -0
  80. adam/commands/export/export_use.py +49 -0
  81. adam/commands/export/exporter.py +332 -0
  82. adam/commands/export/import_files.py +44 -0
  83. adam/commands/export/import_session.py +44 -0
  84. adam/commands/export/importer.py +81 -0
  85. adam/commands/export/importer_athena.py +177 -0
  86. adam/commands/export/importer_sqlite.py +67 -0
  87. adam/commands/export/show_column_counts.py +45 -0
  88. adam/commands/export/show_export_databases.py +38 -0
  89. adam/commands/export/show_export_session.py +39 -0
  90. adam/commands/export/show_export_sessions.py +37 -0
  91. adam/commands/export/utils_export.py +343 -0
  92. adam/commands/find_files.py +51 -0
  93. adam/commands/find_processes.py +76 -0
  94. adam/commands/head.py +36 -0
  95. adam/commands/help.py +14 -9
  96. adam/commands/intermediate_command.py +49 -0
  97. adam/commands/issues.py +14 -40
  98. adam/commands/kubectl.py +38 -0
  99. adam/commands/login.py +26 -25
  100. adam/commands/logs.py +5 -7
  101. adam/commands/ls.py +11 -110
  102. adam/commands/medusa/medusa.py +4 -22
  103. adam/commands/medusa/medusa_backup.py +22 -29
  104. adam/commands/medusa/medusa_restore.py +40 -39
  105. adam/commands/medusa/medusa_show_backupjobs.py +19 -20
  106. adam/commands/medusa/medusa_show_restorejobs.py +15 -20
  107. adam/commands/nodetool.py +11 -15
  108. adam/commands/param_get.py +11 -14
  109. adam/commands/param_set.py +8 -12
  110. adam/commands/postgres/postgres.py +45 -46
  111. adam/commands/postgres/postgres_databases.py +269 -0
  112. adam/commands/postgres/postgres_ls.py +4 -8
  113. adam/commands/postgres/postgres_preview.py +5 -9
  114. adam/commands/postgres/psql_completions.py +4 -3
  115. adam/commands/postgres/utils_postgres.py +70 -0
  116. adam/commands/preview_table.py +10 -61
  117. adam/commands/pwd.py +14 -43
  118. adam/commands/reaper/reaper.py +4 -24
  119. adam/commands/reaper/reaper_forward.py +49 -56
  120. adam/commands/reaper/reaper_forward_session.py +6 -0
  121. adam/commands/reaper/reaper_forward_stop.py +10 -16
  122. adam/commands/reaper/reaper_restart.py +8 -15
  123. adam/commands/reaper/reaper_run_abort.py +8 -33
  124. adam/commands/reaper/reaper_runs.py +43 -58
  125. adam/commands/reaper/reaper_runs_abort.py +29 -49
  126. adam/commands/reaper/reaper_schedule_activate.py +9 -32
  127. adam/commands/reaper/reaper_schedule_start.py +9 -32
  128. adam/commands/reaper/reaper_schedule_stop.py +9 -32
  129. adam/commands/reaper/reaper_schedules.py +4 -14
  130. adam/commands/reaper/reaper_status.py +8 -16
  131. adam/commands/reaper/utils_reaper.py +194 -0
  132. adam/commands/repair/repair.py +4 -22
  133. adam/commands/repair/repair_log.py +6 -12
  134. adam/commands/repair/repair_run.py +29 -36
  135. adam/commands/repair/repair_scan.py +33 -39
  136. adam/commands/repair/repair_stop.py +6 -12
  137. adam/commands/report.py +25 -21
  138. adam/commands/restart.py +27 -28
  139. adam/commands/rollout.py +20 -25
  140. adam/commands/shell.py +12 -4
  141. adam/commands/show/show.py +11 -23
  142. adam/commands/show/show_adam.py +3 -3
  143. adam/commands/show/show_cassandra_repairs.py +35 -0
  144. adam/commands/show/show_cassandra_status.py +34 -52
  145. adam/commands/show/show_cassandra_version.py +5 -18
  146. adam/commands/show/show_commands.py +20 -25
  147. adam/commands/show/show_host.py +33 -0
  148. adam/commands/show/show_login.py +23 -27
  149. adam/commands/show/show_params.py +2 -5
  150. adam/commands/show/show_processes.py +16 -20
  151. adam/commands/show/show_storage.py +10 -20
  152. adam/commands/watch.py +27 -30
  153. adam/config.py +7 -15
  154. adam/embedded_params.py +1 -1
  155. adam/log.py +4 -4
  156. adam/pod_exec_result.py +13 -5
  157. adam/repl.py +126 -119
  158. adam/repl_commands.py +63 -29
  159. adam/repl_state.py +320 -71
  160. adam/sql/sql_completer.py +98 -383
  161. adam/sql/sql_state_machine.py +630 -0
  162. adam/sql/term_completer.py +14 -4
  163. adam/sso/authn_ad.py +6 -8
  164. adam/sso/authn_okta.py +4 -6
  165. adam/sso/cred_cache.py +4 -6
  166. adam/sso/idp.py +10 -13
  167. adam/utils.py +511 -10
  168. adam/utils_athena.py +145 -0
  169. adam/utils_audits.py +102 -0
  170. adam/utils_issues.py +32 -0
  171. adam/utils_k8s/__init__.py +0 -0
  172. adam/utils_k8s/app_clusters.py +28 -0
  173. adam/utils_k8s/app_pods.py +36 -0
  174. adam/utils_k8s/cassandra_clusters.py +44 -0
  175. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +11 -4
  176. adam/{k8s_utils → utils_k8s}/custom_resources.py +16 -17
  177. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  178. adam/{k8s_utils → utils_k8s}/ingresses.py +2 -2
  179. adam/{k8s_utils → utils_k8s}/jobs.py +7 -11
  180. adam/utils_k8s/k8s.py +87 -0
  181. adam/{k8s_utils → utils_k8s}/kube_context.py +2 -2
  182. adam/{k8s_utils → utils_k8s}/pods.py +109 -74
  183. adam/{k8s_utils → utils_k8s}/secrets.py +7 -3
  184. adam/{k8s_utils → utils_k8s}/service_accounts.py +5 -4
  185. adam/{k8s_utils → utils_k8s}/services.py +2 -2
  186. adam/{k8s_utils → utils_k8s}/statefulsets.py +3 -14
  187. adam/utils_local.py +4 -0
  188. adam/utils_net.py +24 -0
  189. adam/utils_repl/__init__.py +0 -0
  190. adam/utils_repl/automata_completer.py +48 -0
  191. adam/utils_repl/repl_completer.py +46 -0
  192. adam/utils_repl/state_machine.py +173 -0
  193. adam/utils_sqlite.py +137 -0
  194. adam/version.py +1 -1
  195. {kaqing-2.0.52.dist-info → kaqing-2.0.184.dist-info}/METADATA +1 -1
  196. kaqing-2.0.184.dist-info/RECORD +244 -0
  197. adam/commands/app.py +0 -67
  198. adam/commands/bash.py +0 -87
  199. adam/commands/cp.py +0 -95
  200. adam/commands/cql/cql_table_completer.py +0 -8
  201. adam/commands/cql/cql_utils.py +0 -109
  202. adam/commands/describe/describe.py +0 -46
  203. adam/commands/describe/describe_keyspace.py +0 -60
  204. adam/commands/describe/describe_keyspaces.py +0 -50
  205. adam/commands/describe/describe_table.py +0 -60
  206. adam/commands/describe/describe_tables.py +0 -50
  207. adam/commands/devices.py +0 -89
  208. adam/commands/postgres/postgres_session.py +0 -240
  209. adam/commands/postgres/postgres_utils.py +0 -31
  210. adam/commands/postgres/psql_table_completer.py +0 -11
  211. adam/commands/reaper/reaper_session.py +0 -159
  212. adam/commands/show/show_app_actions.py +0 -53
  213. adam/commands/show/show_repairs.py +0 -47
  214. adam/k8s_utils/cassandra_clusters.py +0 -35
  215. adam/sql/sql_utils.py +0 -5
  216. kaqing-2.0.52.dist-info/RECORD +0 -184
  217. /adam/commands/{describe → app}/__init__.py +0 -0
  218. /adam/{k8s_utils → commands/audit}/__init__.py +0 -0
  219. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  220. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  221. {kaqing-2.0.52.dist-info → kaqing-2.0.184.dist-info}/WHEEL +0 -0
  222. {kaqing-2.0.52.dist-info → kaqing-2.0.184.dist-info}/entry_points.txt +0 -0
  223. {kaqing-2.0.52.dist-info → kaqing-2.0.184.dist-info}/top_level.txt +0 -0
adam/repl_state.py CHANGED
@@ -1,11 +1,14 @@
1
- import copy
1
+ from copy import copy
2
2
  from enum import Enum
3
-
4
- from adam.commands.postgres.postgres_session import PostgresSession
5
- from adam.k8s_utils.cassandra_clusters import CassandraClusters
6
- from adam.k8s_utils.cassandra_nodes import CassandraNodes
7
- from adam.k8s_utils.kube_context import KubeContext
8
- from adam.k8s_utils.secrets import Secrets
3
+ import re
4
+ from typing import Callable
5
+
6
+ from adam.utils_k8s.app_clusters import AppClusters
7
+ from adam.utils_k8s.app_pods import AppPods
8
+ from adam.utils_k8s.cassandra_clusters import CassandraClusters
9
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
10
+ from adam.utils_k8s.kube_context import KubeContext
11
+ from adam.utils_k8s.secrets import Secrets
9
12
  from adam.utils import display_help, log2, random_alphanumeric
10
13
 
11
14
  class BashSession:
@@ -16,25 +19,8 @@ class BashSession:
16
19
  def pwd(self, state: 'ReplState'):
17
20
  command = f'cat /tmp/.qing-{self.session_id}'
18
21
 
19
- if state.pod:
20
- rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
21
- elif state.sts:
22
- rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
23
-
24
- dir = None
25
- for r in rs:
26
- if r.exit_code(): # if fails to read the session file, ignore
27
- continue
28
-
29
- dir0 = r.stdout.strip(' \r\n')
30
- if dir:
31
- if dir != dir0:
32
- log2('Inconsitent working dir found across multiple pods.')
33
- return None
34
- else:
35
- dir = dir0
36
-
37
- return dir
22
+ with device(state) as pods:
23
+ return pods.exec(command, action='bash', show_out=False)
38
24
 
39
25
  class RequiredState(Enum):
40
26
  CLUSTER = 'cluster'
@@ -43,16 +29,22 @@ class RequiredState(Enum):
43
29
  NAMESPACE = 'namespace'
44
30
  PG_DATABASE = 'pg_database'
45
31
  APP_APP = 'app_app'
32
+ EXPORT_DB = 'export_db'
46
33
 
47
34
  class ReplState:
48
35
  A = 'a'
49
36
  C = 'c'
37
+ L = 'l'
50
38
  P = 'p'
39
+ X = 'x'
40
+
41
+ ANY = [A, C, L, P, X]
42
+ NON_L = [A, C, P, X]
51
43
 
52
44
  def __init__(self, device: str = None,
53
45
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
54
46
  pg_path: str = None,
55
- app_env: str = None, app_app: str = None,
47
+ app_env: str = None, app_app: str = None, app_pod: str = None,
56
48
  in_repl = False, bash_session: BashSession = None, remote_dir = None):
57
49
  self.namespace = KubeContext.in_cluster_namespace()
58
50
 
@@ -62,12 +54,15 @@ class ReplState:
62
54
  self.pg_path = pg_path
63
55
  self.app_env = app_env
64
56
  self.app_app = app_app
57
+ self.app_pod = app_pod
65
58
  if namespace:
66
59
  self.namespace = namespace
67
60
  self.in_repl = in_repl
68
61
  self.bash_session = bash_session
69
62
  self.remote_dir = remote_dir
70
- # self.wait_log_flag = False
63
+ self.original_state: ReplState = None
64
+
65
+ self.export_session: str = None
71
66
 
72
67
  if ns_sts:
73
68
  nn = ns_sts.split('@')
@@ -82,13 +77,51 @@ class ReplState:
82
77
  def __hash__(self):
83
78
  return hash((self.sts, self.pod))
84
79
 
85
- def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True) -> tuple['ReplState', list[str]]:
80
+ def __str__(self):
81
+ msg = ''
82
+ if self.device == ReplState.P:
83
+ msg = f'{ReplState.P}:'
84
+ host, database = self.pg_host_n_database()
85
+ if database:
86
+ msg += database
87
+ elif host:
88
+ msg += host
89
+ elif self.device == ReplState.A:
90
+ msg = f'{ReplState.A}:'
91
+ if self.app_env:
92
+ msg += self.app_env
93
+ if self.app_app:
94
+ msg += f'/{self.app_app}'
95
+ if self.app_pod:
96
+ # azops88-c3-c3-k8sdeploy-appleader-001-79957cf5b6-9k4bw
97
+ group = re.match(r".*?-.*?-.*?-.*?-(.*?-.*?)-.*", self.app_pod)
98
+ msg += '/' + group[1]
99
+ elif self.device == ReplState.L:
100
+ msg = f'{ReplState.L}:'
101
+ elif self.device == ReplState.X:
102
+ msg = f'{ReplState.X}:'
103
+ if self.export_session:
104
+ msg += self.export_session
105
+ else:
106
+ msg = f'{ReplState.C}:'
107
+ if self.pod:
108
+ # cs-d0767a536f-cs-d0767a536f-default-sts-0
109
+ group = re.match(r".*?-.*?-(.*)", self.pod)
110
+ msg += group[1]
111
+ elif self.sts:
112
+ # cs-d0767a536f-cs-d0767a536f-default-sts
113
+ group = re.match(r".*?-.*?-(.*)", self.sts)
114
+ msg += group[1]
115
+
116
+ return msg
117
+
118
+ def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
86
119
  state = self
87
120
 
88
121
  new_args = []
89
122
  for index, arg in enumerate(args):
90
- if index < 6:
91
- state = copy.copy(state)
123
+ if index < args_to_check:
124
+ state = copy(state)
92
125
 
93
126
  s, n = KubeContext.is_sts_name(arg)
94
127
  if s:
@@ -126,10 +159,36 @@ class ReplState:
126
159
  new_args = []
127
160
  for index, arg in enumerate(args):
128
161
  if index < 6:
129
- state = copy.copy(state)
130
-
131
- if arg in [f'{ReplState.A}:', f'{ReplState.C}:', f'{ReplState.P}:']:
132
- state.device = arg.strip(':')
162
+ state = copy(state)
163
+
164
+ groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
165
+ if groups:
166
+ if groups[1] == 'p':
167
+ state.device = 'p'
168
+ state.pg_path = groups[2]
169
+ elif groups[1] == 'c':
170
+ state.device = 'c'
171
+ if path := groups[2]:
172
+ p_and_ns = path.split('@')
173
+ sts_and_pod = p_and_ns[0].split('/')
174
+ state.sts = sts_and_pod[0]
175
+ if len(sts_and_pod) > 1:
176
+ state.pod = sts_and_pod[1]
177
+ if len(p_and_ns) > 1:
178
+ state.namespace = p_and_ns[1]
179
+ elif ns := KubeContext.in_cluster_namespace():
180
+ state.namespace = ns
181
+ elif groups[1] == 'l':
182
+ state.device = 'l'
183
+ elif groups[1] == 'x':
184
+ state.device = 'x'
185
+ else:
186
+ state.device = 'a'
187
+ if path := groups[2]:
188
+ env_and_app = path.split('/')
189
+ state.app_env = env_and_app[0]
190
+ if len(env_and_app) > 1:
191
+ state.app_app = env_and_app[1]
133
192
  else:
134
193
  new_args.append(arg)
135
194
  else:
@@ -140,84 +199,274 @@ class ReplState:
140
199
 
141
200
  return (state, new_args)
142
201
 
143
- def validate(self, required: RequiredState = None, pg_required: RequiredState = None, app_required: RequiredState = None):
144
- if not pg_required and not app_required:
145
- if required == RequiredState.CLUSTER:
146
- if not self.namespace or not self.sts:
202
+ def validate(self, required: list[RequiredState] = [], show_err = True):
203
+ if not required:
204
+ return True
205
+
206
+ def default_err():
207
+ if self.in_repl:
208
+ log2(f'Not a valid command on {self.device}: drive.')
209
+ else:
210
+ log2('* on a wrong device.')
211
+ log2()
212
+ display_help()
213
+
214
+ if type(required) is not list:
215
+ valid, err = self._validate(required)
216
+ if valid:
217
+ return True
218
+
219
+ if show_err:
220
+ if err:
221
+ err()
222
+ else:
223
+ default_err()
224
+
225
+ return False
226
+
227
+ devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
228
+ non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
229
+
230
+ first_error: Callable = None
231
+ for r in non_devices:
232
+ valid, err = self._validate(r)
233
+ if valid:
234
+ return True
235
+
236
+ if not first_error:
237
+ first_error = err
238
+
239
+ if devices:
240
+ valid, err = self._validate_device(devices)
241
+ if valid:
242
+ return True
243
+
244
+ if not first_error:
245
+ first_error = err
246
+
247
+ if show_err and first_error:
248
+ if first_error:
249
+ first_error()
250
+ else:
251
+ default_err()
252
+
253
+ return False
254
+
255
+ def _validate(self, required: RequiredState):
256
+ if required == RequiredState.CLUSTER:
257
+ if self.device != ReplState.C:
258
+ return (False, None)
259
+
260
+ if not self.namespace or not self.sts:
261
+ def error():
147
262
  if self.in_repl:
148
263
  log2('cd to a Cassandra cluster first.')
149
264
  else:
150
265
  log2('* Cassandra cluster is missing.')
151
266
  log2()
152
267
  display_help()
268
+ return (False, error)
269
+
270
+ elif required == RequiredState.POD:
271
+ if self.device != ReplState.C:
272
+ return (False, None)
153
273
 
154
- return False
155
- elif required == RequiredState.POD:
156
- if not self.namespace or not self.pod:
274
+ if not self.namespace or not self.pod:
275
+ def error():
157
276
  if self.in_repl:
158
277
  log2('cd to a pod first.')
159
278
  else:
160
279
  log2('* Pod is missing.')
161
280
  log2()
162
281
  display_help()
282
+ return (False, error)
163
283
 
164
- return False
165
- elif required == RequiredState.CLUSTER_OR_POD:
166
- if not self.namespace or not self.sts and not self.pod:
284
+ elif required == RequiredState.CLUSTER_OR_POD:
285
+ if self.device != ReplState.C:
286
+ return (False, None)
287
+
288
+ if not self.namespace or not self.sts and not self.pod:
289
+ def error():
167
290
  if self.in_repl:
168
291
  log2('cd to a Cassandra cluster first.')
169
292
  else:
170
293
  log2('* Cassandra cluster or pod is missing.')
171
294
  log2()
172
295
  display_help()
296
+ return (False, error)
297
+
298
+ elif required == RequiredState.NAMESPACE:
299
+ if self.device != ReplState.C:
300
+ return (False, None)
173
301
 
174
- return False
175
- elif required == RequiredState.NAMESPACE:
176
- if not self.namespace:
302
+ if not self.namespace:
303
+ def error():
177
304
  if self.in_repl:
178
305
  log2('Namespace is required.')
179
306
  else:
180
307
  log2('* namespace is missing.')
181
308
  log2()
182
309
  display_help()
310
+ return (False, error)
183
311
 
184
- return False
312
+ elif required == RequiredState.PG_DATABASE:
313
+ if self.device != ReplState.P:
314
+ return (False, None)
185
315
 
186
- if pg_required == RequiredState.PG_DATABASE:
187
- pg = PostgresSession(self.namespace, self.pg_path)
188
- if not pg.db:
316
+ _, database = self.pg_host_n_database()
317
+ if not database:
318
+ def error():
319
+ if self.in_repl:
320
+ log2('cd to a database first.')
321
+ else:
322
+ log2('* database is missing.')
323
+ log2()
324
+ display_help()
325
+ return (False, error)
326
+
327
+ elif required == RequiredState.APP_APP:
328
+ if self.device != ReplState.A:
329
+ return (False, None)
330
+
331
+ if not self.app_app:
332
+ def error():
333
+ if self.in_repl:
334
+ log2('cd to an app first.')
335
+ else:
336
+ log2('* app is missing.')
337
+ log2()
338
+ display_help()
339
+ return (False, error)
340
+
341
+ elif required == RequiredState.EXPORT_DB:
342
+ if self.device not in [ReplState.C, ReplState.X]:
343
+ return (False, None)
344
+
345
+ if not self.export_session:
346
+ def error():
347
+ if self.in_repl:
348
+ if self.device == ReplState.C:
349
+ log2("Select an export database first with 'use' command.")
350
+ else:
351
+ log2('cd to an export database first.')
352
+ else:
353
+ log2('* export database is missing.')
354
+ log2()
355
+ display_help()
356
+ return (False, error)
357
+
358
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
359
+ def error():
189
360
  if self.in_repl:
190
- log2('cd to a database first.')
361
+ log2(f'Switch to {required}: first.')
191
362
  else:
192
- log2('* database is missing.')
363
+ log2('* on a wrong device.')
193
364
  log2()
194
365
  display_help()
366
+ return (False, error)
195
367
 
196
- return False
197
-
198
- if app_required == RequiredState.APP_APP and not self.app_app:
199
- if self.in_repl:
200
- log2('cd to an app first.')
201
- else:
202
- log2('* app is missing.')
203
- log2()
204
- display_help()
368
+ return (True, None)
205
369
 
206
- return False
370
+ def _validate_device(self, devices: list[RequiredState]):
371
+ if self.device not in devices:
372
+ def error():
373
+ if self.in_repl:
374
+ log2(f'Not a valid command on {self.device}: drive.')
375
+ else:
376
+ log2('* on a wrong device.')
377
+ log2()
378
+ display_help()
379
+ return (False, error)
207
380
 
208
- return True
381
+ return (True, None)
209
382
 
210
383
  def user_pass(self, secret_path = 'cql.secret'):
211
384
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
212
385
 
213
386
  def enter_bash(self, bash_session: BashSession):
387
+ self.push()
388
+
214
389
  self.bash_session = bash_session
215
- if self.device != ReplState.C:
216
- self.device = ReplState.C
217
- log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
218
390
 
219
391
  def exit_bash(self):
220
- if self.bash_session and self.bash_session.device:
221
- self.device = self.bash_session.device
392
+ self.pop()
393
+ self.bash_session = None
394
+
395
+ def push(self):
396
+ if not self.original_state:
397
+ self.original_state = copy(self)
398
+
399
+ def pop(self):
400
+ if o := self.original_state:
401
+ self.device = o.device
402
+ self.sts = o.sts
403
+ self.pod = o.pod
404
+ self.pg_path = o.pg_path
405
+ self.app_env = o.app_env
406
+ self.app_app = o.app_app
407
+ self.app_pod = o.app_pod
408
+ # self.export_session = o.export_session
409
+ self.namespace = o.namespace
410
+
411
+ self.original_state = None
412
+
413
+ def pg_host_n_database(self):
414
+ host = None
415
+ database = None
416
+
417
+ if self.pg_path:
418
+ host_n_db = self.pg_path.split('/')
419
+ host = host_n_db[0]
420
+ if len(host_n_db) > 1:
421
+ database = host_n_db[1]
422
+
423
+ return host, database
424
+
425
+ class DevicePodService:
426
+ def __init__(self, handler: 'DeviceExecHandler'):
427
+ self.handler = handler
428
+
429
+ def exec(self, command: str, action='bash', show_out = True):
430
+ state = self.handler.state
431
+
432
+ rs = None
433
+ if state.device == ReplState.A and state.app_app:
434
+ if state.app_pod:
435
+ rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=show_out)]
436
+ else:
437
+ pods = AppPods.pod_names(state.namespace, state.app_env, state.app_app)
438
+ rs = AppClusters.exec(pods, state.namespace, command, show_out=show_out)
439
+ elif state.pod:
440
+ rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=show_out)]
441
+ elif state.sts:
442
+ rs = CassandraClusters.exec(state.sts, state.namespace, command, action=action, show_out=show_out)
443
+ # assume that pg-agent or ops pod is single pod
444
+
445
+ dir = None
446
+ if rs:
447
+ for r in rs:
448
+ if r.exit_code(): # if fails to read the session file, ignore
449
+ continue
450
+
451
+ dir0 = r.stdout.strip(' \r\n')
452
+ if dir:
453
+ if dir != dir0:
454
+ log2('Inconsitent working dir found across multiple pods.')
455
+ return None
456
+ else:
457
+ dir = dir0
458
+
459
+ return dir
460
+
461
+ class DeviceExecHandler:
462
+ def __init__(self, state: ReplState):
463
+ self.state = state
464
+
465
+ def __enter__(self):
466
+ return DevicePodService(self)
467
+
468
+ def __exit__(self, exc_type, exc_val, exc_tb):
469
+ return False
222
470
 
223
- self.bash_session = None
471
+ def device(state: ReplState):
472
+ return DeviceExecHandler(state)