kaqing 1.98.15__py3-none-any.whl → 2.0.145__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 (180) hide show
  1. adam/app_session.py +1 -1
  2. adam/apps.py +2 -2
  3. adam/batch.py +30 -31
  4. adam/checks/check_utils.py +4 -4
  5. adam/checks/compactionstats.py +1 -1
  6. adam/checks/cpu.py +2 -2
  7. adam/checks/disk.py +1 -1
  8. adam/checks/gossip.py +1 -1
  9. adam/checks/memory.py +3 -3
  10. adam/checks/status.py +1 -1
  11. adam/commands/alter_tables.py +81 -0
  12. adam/commands/app.py +3 -3
  13. adam/commands/app_ping.py +2 -2
  14. adam/commands/audit/audit.py +86 -0
  15. adam/commands/audit/audit_repair_tables.py +77 -0
  16. adam/commands/audit/audit_run.py +58 -0
  17. adam/commands/audit/show_last10.py +51 -0
  18. adam/commands/audit/show_slow10.py +50 -0
  19. adam/commands/audit/show_top10.py +48 -0
  20. adam/commands/audit/utils_show_top10.py +59 -0
  21. adam/commands/bash/bash.py +133 -0
  22. adam/commands/bash/bash_completer.py +93 -0
  23. adam/commands/cat.py +56 -0
  24. adam/commands/cd.py +12 -82
  25. adam/commands/check.py +6 -0
  26. adam/commands/cli_commands.py +3 -3
  27. adam/commands/code.py +60 -0
  28. adam/commands/command.py +48 -12
  29. adam/commands/commands_utils.py +4 -5
  30. adam/commands/cql/cql_completions.py +28 -0
  31. adam/commands/cql/cql_utils.py +209 -0
  32. adam/commands/{cqlsh.py → cql/cqlsh.py} +15 -10
  33. adam/commands/deploy/__init__.py +0 -0
  34. adam/commands/{frontend → deploy}/code_start.py +1 -1
  35. adam/commands/{frontend → deploy}/code_stop.py +1 -1
  36. adam/commands/{frontend → deploy}/code_utils.py +2 -2
  37. adam/commands/deploy/deploy.py +48 -0
  38. adam/commands/deploy/deploy_frontend.py +52 -0
  39. adam/commands/deploy/deploy_pg_agent.py +38 -0
  40. adam/commands/deploy/deploy_pod.py +110 -0
  41. adam/commands/deploy/deploy_utils.py +29 -0
  42. adam/commands/deploy/undeploy.py +48 -0
  43. adam/commands/deploy/undeploy_frontend.py +41 -0
  44. adam/commands/deploy/undeploy_pg_agent.py +42 -0
  45. adam/commands/deploy/undeploy_pod.py +51 -0
  46. adam/commands/devices/__init__.py +0 -0
  47. adam/commands/devices/device.py +27 -0
  48. adam/commands/devices/device_app.py +146 -0
  49. adam/commands/devices/device_auit_log.py +43 -0
  50. adam/commands/devices/device_cass.py +145 -0
  51. adam/commands/devices/device_export.py +86 -0
  52. adam/commands/devices/device_postgres.py +109 -0
  53. adam/commands/devices/devices.py +25 -0
  54. adam/commands/export/__init__.py +0 -0
  55. adam/commands/export/clean_up_export_session.py +53 -0
  56. adam/commands/{frontend/teardown_frontend.py → export/clean_up_export_sessions.py} +9 -11
  57. adam/commands/export/drop_export_database.py +58 -0
  58. adam/commands/export/drop_export_databases.py +46 -0
  59. adam/commands/export/export.py +83 -0
  60. adam/commands/export/export_databases.py +170 -0
  61. adam/commands/export/export_select.py +85 -0
  62. adam/commands/export/export_select_x.py +54 -0
  63. adam/commands/export/export_use.py +55 -0
  64. adam/commands/export/exporter.py +364 -0
  65. adam/commands/export/import_session.py +68 -0
  66. adam/commands/export/importer.py +67 -0
  67. adam/commands/export/importer_athena.py +80 -0
  68. adam/commands/export/importer_sqlite.py +47 -0
  69. adam/commands/export/show_column_counts.py +63 -0
  70. adam/commands/export/show_export_databases.py +39 -0
  71. adam/commands/export/show_export_session.py +51 -0
  72. adam/commands/export/show_export_sessions.py +47 -0
  73. adam/commands/export/utils_export.py +291 -0
  74. adam/commands/help.py +12 -7
  75. adam/commands/issues.py +6 -0
  76. adam/commands/kubectl.py +41 -0
  77. adam/commands/login.py +9 -5
  78. adam/commands/logs.py +2 -1
  79. adam/commands/ls.py +4 -107
  80. adam/commands/medusa/medusa.py +2 -26
  81. adam/commands/medusa/medusa_backup.py +2 -2
  82. adam/commands/medusa/medusa_restore.py +3 -4
  83. adam/commands/medusa/medusa_show_backupjobs.py +4 -3
  84. adam/commands/medusa/medusa_show_restorejobs.py +3 -3
  85. adam/commands/nodetool.py +9 -4
  86. adam/commands/param_set.py +1 -1
  87. adam/commands/postgres/postgres.py +42 -43
  88. adam/commands/postgres/postgres_context.py +248 -0
  89. adam/commands/postgres/postgres_preview.py +0 -1
  90. adam/commands/postgres/postgres_utils.py +31 -0
  91. adam/commands/postgres/psql_completions.py +10 -0
  92. adam/commands/preview_table.py +18 -40
  93. adam/commands/pwd.py +2 -28
  94. adam/commands/reaper/reaper.py +4 -24
  95. adam/commands/reaper/reaper_restart.py +1 -1
  96. adam/commands/reaper/reaper_session.py +2 -2
  97. adam/commands/repair/repair.py +3 -27
  98. adam/commands/repair/repair_log.py +1 -1
  99. adam/commands/repair/repair_run.py +2 -2
  100. adam/commands/repair/repair_scan.py +2 -7
  101. adam/commands/repair/repair_stop.py +1 -1
  102. adam/commands/report.py +6 -0
  103. adam/commands/restart.py +2 -2
  104. adam/commands/rollout.py +1 -1
  105. adam/commands/shell.py +33 -0
  106. adam/commands/show/show.py +11 -26
  107. adam/commands/show/show_app_actions.py +3 -0
  108. adam/commands/show/show_app_id.py +1 -1
  109. adam/commands/show/show_app_queues.py +3 -2
  110. adam/commands/show/show_cassandra_status.py +3 -3
  111. adam/commands/show/show_cassandra_version.py +3 -3
  112. adam/commands/show/show_commands.py +4 -1
  113. adam/commands/show/show_host.py +33 -0
  114. adam/commands/show/show_login.py +3 -0
  115. adam/commands/show/show_processes.py +1 -1
  116. adam/commands/show/show_repairs.py +2 -2
  117. adam/commands/show/show_storage.py +1 -1
  118. adam/commands/watch.py +1 -1
  119. adam/config.py +16 -3
  120. adam/embedded_params.py +1 -1
  121. adam/pod_exec_result.py +10 -2
  122. adam/repl.py +132 -117
  123. adam/repl_commands.py +62 -18
  124. adam/repl_state.py +276 -55
  125. adam/sql/__init__.py +0 -0
  126. adam/sql/sql_completer.py +120 -0
  127. adam/sql/sql_state_machine.py +617 -0
  128. adam/sql/term_completer.py +76 -0
  129. adam/sso/authenticator.py +1 -1
  130. adam/sso/authn_ad.py +36 -56
  131. adam/sso/authn_okta.py +6 -32
  132. adam/sso/cred_cache.py +1 -1
  133. adam/sso/idp.py +74 -9
  134. adam/sso/idp_login.py +2 -2
  135. adam/sso/idp_session.py +10 -7
  136. adam/utils.py +85 -4
  137. adam/utils_athena.py +145 -0
  138. adam/utils_audits.py +102 -0
  139. adam/utils_k8s/__init__.py +0 -0
  140. adam/utils_k8s/app_clusters.py +33 -0
  141. adam/utils_k8s/app_pods.py +31 -0
  142. adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -21
  143. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
  144. adam/utils_k8s/config_maps.py +34 -0
  145. adam/utils_k8s/deployment.py +56 -0
  146. adam/{k8s_utils → utils_k8s}/jobs.py +1 -1
  147. adam/{k8s_utils → utils_k8s}/kube_context.py +1 -1
  148. adam/utils_k8s/pods.py +342 -0
  149. adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
  150. adam/utils_k8s/service_accounts.py +169 -0
  151. adam/{k8s_utils → utils_k8s}/statefulsets.py +5 -4
  152. adam/{k8s_utils → utils_k8s}/volumes.py +9 -0
  153. adam/utils_net.py +24 -0
  154. adam/utils_repl/__init__.py +0 -0
  155. adam/utils_repl/automata_completer.py +48 -0
  156. adam/utils_repl/repl_completer.py +46 -0
  157. adam/utils_repl/state_machine.py +173 -0
  158. adam/utils_sqlite.py +101 -0
  159. adam/version.py +1 -1
  160. {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/METADATA +1 -1
  161. kaqing-2.0.145.dist-info/RECORD +227 -0
  162. adam/commands/bash.py +0 -87
  163. adam/commands/cql_utils.py +0 -53
  164. adam/commands/devices.py +0 -89
  165. adam/commands/frontend/setup.py +0 -60
  166. adam/commands/frontend/setup_frontend.py +0 -58
  167. adam/commands/frontend/teardown.py +0 -61
  168. adam/commands/postgres/postgres_session.py +0 -225
  169. adam/commands/user_entry.py +0 -77
  170. adam/k8s_utils/pods.py +0 -211
  171. kaqing-1.98.15.dist-info/RECORD +0 -160
  172. /adam/commands/{frontend → audit}/__init__.py +0 -0
  173. /adam/{k8s_utils → commands/bash}/__init__.py +0 -0
  174. /adam/{medusa_show_restorejobs.py → commands/cql/__init__.py} +0 -0
  175. /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
  176. /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
  177. /adam/{k8s_utils → utils_k8s}/services.py +0 -0
  178. {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/WHEEL +0 -0
  179. {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/entry_points.txt +0 -0
  180. {kaqing-1.98.15.dist-info → kaqing-2.0.145.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,86 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.devices.device import Device
3
+ from adam.commands.export.export_databases import ExportDatabases
4
+ from adam.config import Config
5
+ from adam.repl_state import ReplState
6
+ from adam.utils import lines_to_tabular, log, log2
7
+
8
+ class DeviceExport(Command, Device):
9
+ COMMAND = f'{ReplState.X}:'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(DeviceExport, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return DeviceExport.COMMAND
22
+
23
+ def run(self, cmd: str, state: ReplState):
24
+ if not self.args(cmd):
25
+ return super().run(cmd, state)
26
+
27
+ state.device = ReplState.X
28
+
29
+ return state
30
+
31
+ def completion(self, state: ReplState):
32
+ return super().completion(state)
33
+
34
+ def help(self, _: ReplState):
35
+ return f'{DeviceExport.COMMAND}\t move to Export Database Operations device'
36
+
37
+ def ls(self, cmd: str, state: ReplState):
38
+ if state.export_session:
39
+ self.show_export_tables(state.export_session)
40
+ else:
41
+ self.show_export_databases()
42
+
43
+ def show_export_databases(self, importer: str = None):
44
+ lines = [f'{k}\t{v}' for k, v in ExportDatabases.database_names_with_keyspace_cnt(importer).items()]
45
+ log(lines_to_tabular(lines, 'NAME\tKEYSPACES', separator='\t'))
46
+
47
+ def show_export_tables(self, export_session: str):
48
+ log(lines_to_tabular(ExportDatabases.table_names(export_session), 'NAME', separator=','))
49
+
50
+ def cd(self, dir: str, state: ReplState):
51
+ if dir in ['', '..']:
52
+ state.export_session = None
53
+ else:
54
+ state.export_session = dir
55
+
56
+ def cd_completion(self, cmd: str, state: ReplState, default: dict = {}):
57
+ if state.export_session:
58
+ return {cmd: {'..': None} | {n: None for n in ExportDatabases.database_names()}}
59
+ else:
60
+ return {cmd: {n: None for n in ExportDatabases.database_names()}}
61
+
62
+ def pwd(self, state: ReplState):
63
+ words = []
64
+
65
+ if state.export_session:
66
+ words.append(state.export_session)
67
+
68
+ return '\t'.join([f'{ReplState.X}:>'] + (words if words else ['/']))
69
+
70
+ def try_fallback_action(self, chain: Command, state: ReplState, cmd: str):
71
+ result = chain.run(f'.{cmd}', state)
72
+ if type(result) is ReplState:
73
+ if state.export_session and not result.export_session:
74
+ state.export_session = None
75
+
76
+ return True, result
77
+
78
+ def enter(self, state: ReplState):
79
+ if auto_enter := Config().get('repl.x.auto-enter', 'no'):
80
+ if auto_enter == 'latest':
81
+ Config().wait_log(f'Moving to the latest export database...')
82
+ if dbs := ExportDatabases.database_names():
83
+ state.export_session = sorted(dbs)[-1]
84
+ else:
85
+ log2('No export database found.')
86
+
@@ -0,0 +1,109 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.devices.device import Device
3
+ from adam.commands.postgres.postgres_context import PostgresContext
4
+ from adam.commands.postgres.postgres_utils import pg_database_names, pg_table_names
5
+ from adam.config import Config
6
+ from adam.repl_state import ReplState
7
+ from adam.utils import lines_to_tabular, log
8
+
9
+ class DevicePostgres(Command, Device):
10
+ COMMAND = f'{ReplState.P}:'
11
+
12
+ # the singleton pattern
13
+ def __new__(cls, *args, **kwargs):
14
+ if not hasattr(cls, 'instance'): cls.instance = super(DevicePostgres, cls).__new__(cls)
15
+
16
+ return cls.instance
17
+
18
+ def __init__(self, successor: Command=None):
19
+ super().__init__(successor)
20
+
21
+ def command(self):
22
+ return DevicePostgres.COMMAND
23
+
24
+ def run(self, cmd: str, state: ReplState):
25
+ if not self.args(cmd):
26
+ return super().run(cmd, state)
27
+
28
+ state.device = ReplState.P
29
+
30
+ return state
31
+
32
+ def completion(self, state: ReplState):
33
+ return super().completion(state)
34
+
35
+ def help(self, _: ReplState):
36
+ return f'{DevicePostgres.COMMAND}\t move to Postgres Operations device'
37
+
38
+ def ls(self, cmd: str, state: ReplState):
39
+ if state.pg_path:
40
+ pg: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path)
41
+ if pg.db:
42
+ self.show_pg_tables(pg)
43
+ else:
44
+ self.show_pg_databases(pg)
45
+ else:
46
+ self.show_pg_hosts(state)
47
+
48
+ def show_pg_hosts(self, state: ReplState):
49
+ if state.namespace:
50
+ def line(pg: PostgresContext):
51
+ return f'{pg.path()},{pg.endpoint()}:{pg.port()},{pg.username()},{pg.password()}'
52
+
53
+ lines = [line(PostgresContext.apply(state.namespace, pg)) for pg in PostgresContext.hosts(state.namespace)]
54
+
55
+ log(lines_to_tabular(lines, 'NAME,ENDPOINT,USERNAME,PASSWORD', separator=','))
56
+ else:
57
+ def line(pg: PostgresContext):
58
+ return f'{pg.path()},{pg.namespace},{pg.endpoint()}:{pg.port()},{pg.username()},{pg.password()}'
59
+
60
+ lines = [line(PostgresContext.apply(state.namespace, pg)) for pg in PostgresContext.hosts(state.namespace)]
61
+
62
+ log(lines_to_tabular(lines, 'NAME,NAMESPACE,ENDPOINT,USERNAME,PASSWORD', separator=','))
63
+
64
+ def show_pg_databases(self, pg: PostgresContext):
65
+ log(lines_to_tabular(pg_database_names(pg.namespace, pg.path()), 'DATABASE', separator=','))
66
+
67
+ def show_pg_tables(self, pg: PostgresContext):
68
+ log(lines_to_tabular(pg_table_names(pg.namespace, pg.path()), 'NAME', separator=','))
69
+
70
+ def cd(self, dir: str, state: ReplState):
71
+ if dir == '':
72
+ state.pg_path = None
73
+ else:
74
+ context: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path, arg=dir)
75
+ # patch up state.namespace from pg cd
76
+ if not state.namespace and context.namespace:
77
+ state.namespace = context.namespace
78
+ state.pg_path = context.path()
79
+
80
+ def cd_completion(self, cmd: str, state: ReplState, default: dict = {}):
81
+ pg: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path) if state.pg_path else None
82
+ if pg and pg.db:
83
+ return {cmd: {'..': None}}
84
+ elif pg and pg.host:
85
+ return {cmd: {'..': None} | {p: None for p in pg_database_names(state.namespace, pg.path())}}
86
+ else:
87
+ return {cmd: {p: None for p in PostgresContext.hosts(state.namespace)}}
88
+
89
+ def pwd(self, state: ReplState):
90
+ words = []
91
+
92
+ pg: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path)
93
+
94
+ if pg.host:
95
+ words.append(f'host/{pg.host}')
96
+ if pg.db:
97
+ words.append(f'database/{pg.db}')
98
+
99
+ return '\t'.join([f'{ReplState.P}:>'] + (words if words else ['/']))
100
+
101
+ def try_fallback_action(self, chain: Command, state: ReplState, cmd: str):
102
+ pg: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path)
103
+ if pg.db:
104
+ return True, chain.run(f'pg {cmd}', state)
105
+
106
+ return False, None
107
+
108
+ def enter(self, state: ReplState):
109
+ Config().wait_log('Inspecting postgres database instances...')
@@ -0,0 +1,25 @@
1
+ from adam.commands.devices.device import Device
2
+ from adam.commands.devices.device_app import DeviceApp
3
+ from adam.commands.devices.device_auit_log import DeviceAuditLog
4
+ from adam.commands.devices.device_cass import DeviceCass
5
+ from adam.commands.devices.device_export import DeviceExport
6
+ from adam.commands.devices.device_postgres import DevicePostgres
7
+ from adam.repl_state import ReplState
8
+
9
+ class Devices:
10
+ def device(state: ReplState) -> Device:
11
+ if state.device == ReplState.A:
12
+ return DeviceApp()
13
+ elif state.device == ReplState.C:
14
+ return DeviceCass()
15
+ elif state.device == ReplState.L:
16
+ return DeviceAuditLog()
17
+ elif state.device == ReplState.P:
18
+ return DevicePostgres()
19
+ elif state.device == ReplState.X:
20
+ return DeviceExport()
21
+
22
+ return DeviceCass()
23
+
24
+ def all():
25
+ return [DeviceApp(), DeviceCass(), DeviceAuditLog(), DevicePostgres(), DeviceExport()]
File without changes
@@ -0,0 +1,53 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.export.exporter import Exporter
3
+ from adam.repl_state import ReplState, RequiredState
4
+ from adam.utils import log, log2
5
+
6
+ class CleanUpExportSession(Command):
7
+ COMMAND = 'clean up export session'
8
+
9
+ # the singleton pattern
10
+ def __new__(cls, *args, **kwargs):
11
+ if not hasattr(cls, 'instance'): cls.instance = super(CleanUpExportSession, cls).__new__(cls)
12
+
13
+ return cls.instance
14
+
15
+ def __init__(self, successor: Command=None):
16
+ super().__init__(successor)
17
+
18
+ def command(self):
19
+ return CleanUpExportSession.COMMAND
20
+
21
+ def required(self):
22
+ return RequiredState.CLUSTER_OR_POD
23
+
24
+ def run(self, cmd: str, state: ReplState):
25
+ if not(args := self.args(cmd)):
26
+ return super().run(cmd, state)
27
+
28
+ state, args = self.apply_state(args, state)
29
+ if not self.validate_state(state):
30
+ return state
31
+
32
+ if not args:
33
+ if state.in_repl:
34
+ log2('Specify export session name.')
35
+ else:
36
+ log2('* Session name is missing.')
37
+
38
+ Command.display_help()
39
+
40
+ return 'command-missing'
41
+
42
+ csv_cnt, log_cnt = Exporter.clean_up_session(state.sts, state.pod, state.namespace, args[0])
43
+ log(f'Removed {csv_cnt} csv and {log_cnt} log files.')
44
+
45
+ Exporter.clear_export_session_cache()
46
+
47
+ return state
48
+
49
+ def completion(self, _: ReplState):
50
+ return {}
51
+
52
+ def help(self, _: ReplState):
53
+ return f'{CleanUpExportSession.COMMAND} <export-session-name>\t clean up export session'
@@ -1,14 +1,13 @@
1
1
  from adam.commands.command import Command
2
- from adam.k8s_utils.ingresses import Ingresses
3
- from adam.k8s_utils.services import Services
2
+ from adam.commands.export.exporter import Exporter
4
3
  from adam.repl_state import ReplState, RequiredState
5
4
 
6
- class TearDownFrontend(Command):
7
- COMMAND = 'teardown frontend'
5
+ class CleanUpExportSessions(Command):
6
+ COMMAND = 'clean up all export sessions'
8
7
 
9
8
  # the singleton pattern
10
9
  def __new__(cls, *args, **kwargs):
11
- if not hasattr(cls, 'instance'): cls.instance = super(TearDownFrontend, cls).__new__(cls)
10
+ if not hasattr(cls, 'instance'): cls.instance = super(CleanUpExportSessions, cls).__new__(cls)
12
11
 
13
12
  return cls.instance
14
13
 
@@ -16,10 +15,10 @@ class TearDownFrontend(Command):
16
15
  super().__init__(successor)
17
16
 
18
17
  def command(self):
19
- return TearDownFrontend.COMMAND
18
+ return CleanUpExportSessions.COMMAND
20
19
 
21
20
  def required(self):
22
- return RequiredState.NAMESPACE
21
+ return RequiredState.CLUSTER_OR_POD
23
22
 
24
23
  def run(self, cmd: str, state: ReplState):
25
24
  if not(args := self.args(cmd)):
@@ -29,9 +28,8 @@ class TearDownFrontend(Command):
29
28
  if not self.validate_state(state):
30
29
  return state
31
30
 
32
- name = 'ops'
33
- Ingresses.delete_ingress(name, state.namespace)
34
- Services.delete_service(name, state.namespace)
31
+ if Exporter.clean_up_all_sessions(state.sts, state.pod, state.namespace):
32
+ Exporter.clear_export_session_cache()
35
33
 
36
34
  return state
37
35
 
@@ -39,4 +37,4 @@ class TearDownFrontend(Command):
39
37
  return {}
40
38
 
41
39
  def help(self, _: ReplState):
42
- return f'{TearDownFrontend.COMMAND}\t tear down frontend'
40
+ return f'{CleanUpExportSessions.COMMAND}\t clean up all export sessions'
@@ -0,0 +1,58 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.export.exporter import Exporter
3
+ from adam.repl_state import ReplState
4
+ from adam.utils import log2
5
+ from adam.utils_athena import Athena
6
+ from adam.utils_sqlite import SQLite
7
+
8
+ class DropExportDatabase(Command):
9
+ COMMAND = 'drop export database'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(DropExportDatabase, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return DropExportDatabase.COMMAND
22
+
23
+ def required(self):
24
+ return [ReplState.C, ReplState.X]
25
+
26
+ def run(self, cmd: str, state: ReplState):
27
+ if not(args := self.args(cmd)):
28
+ return super().run(cmd, state)
29
+
30
+ state, args = self.apply_state(args, state)
31
+ if not self.validate_state(state):
32
+ return state
33
+
34
+ if not len(args):
35
+ if state.in_repl:
36
+ log2('Database name is required.')
37
+ log2()
38
+ else:
39
+ log2('* Database name is missing.')
40
+ Command.display_help()
41
+
42
+ return 'command-missing'
43
+
44
+ Exporter.drop_databases(state.sts, state.pod, state.namespace, args[0])
45
+
46
+ SQLite.clear_cache()
47
+ Athena.clear_cache()
48
+
49
+ if state.export_session == args[0]:
50
+ state.export_session = None
51
+
52
+ return state
53
+
54
+ def completion(self, _: ReplState):
55
+ return {}
56
+
57
+ def help(self, _: ReplState):
58
+ return f'{DropExportDatabase.COMMAND} <export-database-name>\t drop export database'
@@ -0,0 +1,46 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.export.exporter import Exporter
3
+ from adam.repl_state import ReplState
4
+ from adam.utils_athena import Athena
5
+ from adam.utils_sqlite import SQLite
6
+
7
+ class DropExportDatabases(Command):
8
+ COMMAND = 'drop all export databases'
9
+
10
+ # the singleton pattern
11
+ def __new__(cls, *args, **kwargs):
12
+ if not hasattr(cls, 'instance'): cls.instance = super(DropExportDatabases, cls).__new__(cls)
13
+
14
+ return cls.instance
15
+
16
+ def __init__(self, successor: Command=None):
17
+ super().__init__(successor)
18
+
19
+ def command(self):
20
+ return DropExportDatabases.COMMAND
21
+
22
+ def required(self):
23
+ return [ReplState.C, ReplState.X]
24
+
25
+ def run(self, cmd: str, state: ReplState):
26
+ if not(args := self.args(cmd)):
27
+ return super().run(cmd, state)
28
+
29
+ state, args = self.apply_state(args, state)
30
+ if not self.validate_state(state):
31
+ return state
32
+
33
+ Exporter.drop_databases(state.sts, state.pod, state.namespace)
34
+
35
+ SQLite.clear_cache()
36
+ Athena.clear_cache()
37
+
38
+ state.export_session = None
39
+
40
+ return state
41
+
42
+ def completion(self, _: ReplState):
43
+ return {}
44
+
45
+ def help(self, _: ReplState):
46
+ return f'{DropExportDatabases.COMMAND}\t drop all export databases'
@@ -0,0 +1,83 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.cql.cql_utils import cassandra_keyspaces, cassandra_table_names
3
+ from adam.commands.export.export_databases import ExportDatabases
4
+ from adam.commands.export.exporter import Exporter
5
+ from adam.commands.export.utils_export import ExportSpec
6
+ from adam.repl_state import ReplState, RequiredState
7
+ from adam.sql.sql_completer import SqlCompleter, SqlVariant
8
+ from adam.utils import log
9
+ from adam.utils_k8s.statefulsets import StatefulSets
10
+
11
+ class ExportTables(Command):
12
+ COMMAND = 'export'
13
+
14
+ # the singleton pattern
15
+ def __new__(cls, *args, **kwargs):
16
+ if not hasattr(cls, 'instance'): cls.instance = super(ExportTables, cls).__new__(cls)
17
+
18
+ return cls.instance
19
+
20
+ def __init__(self, successor: Command=None):
21
+ super().__init__(successor)
22
+
23
+ def command(self):
24
+ return ExportTables.COMMAND
25
+
26
+ def required(self):
27
+ return RequiredState.CLUSTER_OR_POD
28
+
29
+ def run(self, cmd: str, state: ReplState):
30
+ if not(args := self.args(cmd)):
31
+ return super().run(cmd, state)
32
+
33
+ state, args = self.apply_state(args, state)
34
+ if not self.validate_state(state):
35
+ return state
36
+
37
+ # --export-only for testing only
38
+ args, export_only = Command.extract_options(args, ['--export-only'])
39
+
40
+ if not state.pod:
41
+ state.push()
42
+ state.pod = StatefulSets.pod_names(state.sts, state.namespace)[0]
43
+
44
+ export_session = state.export_session
45
+ spec: ExportSpec = None
46
+ try:
47
+ statuses, spec = Exporter.export_tables(args, state, export_only=export_only)
48
+ if not statuses:
49
+ return state
50
+
51
+ Exporter.clear_export_session_cache()
52
+
53
+ if spec.importer == 'csv' or export_only:
54
+ ExportDatabases.disply_export_session(state.sts, state.pod, state.namespace, spec.session)
55
+ else:
56
+ log()
57
+ ExportDatabases.display_export_db(state.export_session)
58
+
59
+ finally:
60
+ state.pop()
61
+
62
+ # if exporting to csv, do not bind the new session id to repl state
63
+ if spec and spec.importer == 'csv':
64
+ state.export_session = export_session
65
+
66
+ return state
67
+
68
+ def completion(self, state: ReplState):
69
+ def sc():
70
+ return SqlCompleter(lambda: cassandra_table_names(state), expandables={
71
+ 'dml':'export',
72
+ 'columns': lambda table: ['id', '*'],
73
+ 'keyspaces': lambda: cassandra_keyspaces(state),
74
+ 'export-dbs': lambda: ExportDatabases.database_names(),
75
+ }, variant=SqlVariant.CQL)
76
+
77
+ if state.sts:
78
+ return {f'@{p}': {ExportTables.COMMAND: sc()} for p in StatefulSets.pod_names(state.sts, state.namespace)}
79
+
80
+ return {}
81
+
82
+ def help(self, _: ReplState):
83
+ return f'{ExportTables.COMMAND} [* [in KEYSPACE]] | [TABLE] [as target-name] [with consistency <level>]\t export tables to Sqlite, Athena or CSV file'
@@ -0,0 +1,170 @@
1
+ import os
2
+ import boto3
3
+
4
+ from adam.commands.export.utils_export import ExportTableStatus
5
+ from adam.config import Config
6
+ from adam.utils import lines_to_tabular, log, log2, ing
7
+ from adam.utils_athena import Athena
8
+ from adam.utils_k8s.statefulsets import StatefulSets
9
+ from adam.utils_sqlite import SQLite
10
+
11
+ LIKE = 'e%_%'
12
+
13
+ class ExportDatabases:
14
+ def run_query(query: str, database: str, show_query=False) -> int:
15
+ cnt: int = 0
16
+
17
+ if show_query:
18
+ log2(query)
19
+
20
+ if database.startswith('s'):
21
+ cnt += SQLite.run_query(query, database=database)
22
+ else:
23
+ cnt += Athena.run_query(query, database=database)
24
+
25
+ return cnt
26
+
27
+ def sessions_from_dbs(dbs: list[str]):
28
+
29
+ sessions = set()
30
+
31
+ for db in dbs:
32
+ sessions.add(db.split('_')[0])
33
+
34
+ return list(sessions)
35
+
36
+ def drop_export_dbs(db: str = None):
37
+ dbs: list[str] = []
38
+
39
+ if not db or db.startswith('s'):
40
+ dbs.extend(ExportDatabases.drop_sqlite_dbs(db))
41
+ if not db or db.startswith('e'):
42
+ dbs.extend(ExportDatabases.drop_athena_dbs(db))
43
+
44
+ return dbs
45
+
46
+ def drop_sqlite_dbs(db: str = None):
47
+ dbs = SQLite.database_names(db)
48
+ if dbs:
49
+ with ing(f'Droping {len(dbs)} SQLite databases'):
50
+ try:
51
+ for db in dbs:
52
+ file_path = f'{SQLite.local_db_dir()}/{db}'
53
+ try:
54
+ os.remove(file_path)
55
+ except OSError as e:
56
+ pass
57
+ except:
58
+ pass
59
+
60
+ return dbs
61
+
62
+ def drop_athena_dbs(db: str = None):
63
+ dbs = Athena.database_names(f'{db}_%' if db else LIKE)
64
+ if dbs:
65
+ with ing(f'Droping {len(dbs)} Athena databases'):
66
+ for db in dbs:
67
+ query = f'DROP DATABASE {db} CASCADE'
68
+ if Config().is_debug():
69
+ log2(query)
70
+ Athena.query(query)
71
+
72
+ with ing(f'Deleting s3 folder: export'):
73
+ try:
74
+ if not db:
75
+ db = ''
76
+
77
+ s3 = boto3.resource('s3')
78
+ bucket = s3.Bucket(Config().get('export.bucket', 'c3.ops--qing'))
79
+ bucket.objects.filter(Prefix=f'export/{db}').delete()
80
+ except:
81
+ pass
82
+
83
+ return dbs
84
+
85
+ def display_export_db(export_session: str):
86
+ if not export_session:
87
+ return
88
+
89
+ Athena.clear_cache()
90
+
91
+ keyspaces = {}
92
+ for table in ExportDatabases.table_names(export_session):
93
+ keyspace = table.split('.')[0]
94
+ if keyspace in keyspaces:
95
+ keyspaces[keyspace] += 1
96
+ else:
97
+ keyspaces[keyspace] = 1
98
+
99
+ log(lines_to_tabular([f'{k},{v}' for k, v in keyspaces.items()], header='SCHEMA,# of TABLES', separator=','))
100
+
101
+ def disply_export_session(sts: str, pod: str, namespace: str, session: str):
102
+ if not pod:
103
+ pod = StatefulSets.pod_names(sts, namespace)[0]
104
+
105
+ if not pod:
106
+ return
107
+
108
+ tables, _ = ExportTableStatus.from_session(sts, pod, namespace, session)
109
+ log()
110
+ log(lines_to_tabular([f'{table.keyspace}\t{table.table}\t{table.target_table}\t{"export_completed_pending_import" if table.status == "pending_import" else table.status}' for table in tables], header='KEYSPACE\tTABLE\tTARGET_TABLE\tSTATUS', separator='\t'))
111
+
112
+ def database_names():
113
+ return ExportDatabases.copy_database_names() + ExportDatabases.export_database_names()
114
+
115
+ def copy_database_names():
116
+ return list({n.split('_')[0] for n in SQLite.database_names()})
117
+
118
+ def export_database_names():
119
+ return list({n.split('_')[0] for n in Athena.database_names(LIKE)})
120
+
121
+ def database_names_with_keyspace_cnt(importer: str = None):
122
+ r = {}
123
+
124
+ names = []
125
+ if not importer:
126
+ names = SQLite.database_names() + Athena.database_names(LIKE)
127
+ elif importer == 'athena':
128
+ names = Athena.database_names(LIKE)
129
+ else:
130
+ names = SQLite.database_names()
131
+
132
+ for n in names:
133
+ tokens = n.split('_')
134
+ name = tokens[0]
135
+ keyspace = None
136
+ if len(tokens) > 1:
137
+ keyspace = tokens[1].replace('.db', '')
138
+
139
+ if keyspace == 'root':
140
+ continue
141
+
142
+ if name in r:
143
+ r[name] += 1
144
+ else:
145
+ r[name] = 1
146
+
147
+ return r
148
+
149
+ def table_names(session: str):
150
+ tables = []
151
+
152
+ for session in ExportDatabases._session_database_names(session):
153
+ if session.startswith('s'):
154
+ for table in SQLite.table_names(database=session):
155
+ tables.append(f'{SQLite.keyspace(session)}.{table}')
156
+ else:
157
+ for table in Athena.table_names(database=session, function='export'):
158
+ tables.append(f'{session}.{table}')
159
+
160
+ return tables
161
+
162
+ def _session_database_names(db: str):
163
+ eprefix = db
164
+ if '_' in db:
165
+ eprefix = db.split('_')[0]
166
+
167
+ if db.startswith('s'):
168
+ return SQLite.database_names(prefix=f'{eprefix}_')
169
+ else:
170
+ return Athena.database_names(like=f'{eprefix}_%')