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