kaqing 2.0.14__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 (163) hide show
  1. adam/apps.py +2 -2
  2. adam/batch.py +13 -3
  3. adam/checks/check_utils.py +4 -4
  4. adam/checks/compactionstats.py +1 -1
  5. adam/checks/cpu.py +2 -2
  6. adam/checks/disk.py +1 -1
  7. adam/checks/gossip.py +1 -1
  8. adam/checks/memory.py +3 -3
  9. adam/checks/status.py +1 -1
  10. adam/commands/alter_tables.py +81 -0
  11. adam/commands/app.py +3 -3
  12. adam/commands/app_ping.py +2 -2
  13. adam/commands/audit/audit.py +86 -0
  14. adam/commands/audit/audit_repair_tables.py +77 -0
  15. adam/commands/audit/audit_run.py +58 -0
  16. adam/commands/audit/show_last10.py +51 -0
  17. adam/commands/audit/show_slow10.py +50 -0
  18. adam/commands/audit/show_top10.py +48 -0
  19. adam/commands/audit/utils_show_top10.py +59 -0
  20. adam/commands/bash/__init__.py +0 -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/__init__.py +0 -0
  31. adam/commands/cql/cql_completions.py +28 -0
  32. adam/commands/cql/cql_utils.py +209 -0
  33. adam/commands/{cqlsh.py → cql/cqlsh.py} +15 -10
  34. adam/commands/deploy/code_utils.py +2 -2
  35. adam/commands/deploy/deploy.py +8 -21
  36. adam/commands/deploy/deploy_frontend.py +1 -1
  37. adam/commands/deploy/deploy_pg_agent.py +3 -3
  38. adam/commands/deploy/deploy_pod.py +28 -27
  39. adam/commands/deploy/deploy_utils.py +16 -26
  40. adam/commands/deploy/undeploy.py +8 -21
  41. adam/commands/deploy/undeploy_frontend.py +1 -1
  42. adam/commands/deploy/undeploy_pg_agent.py +5 -3
  43. adam/commands/deploy/undeploy_pod.py +12 -10
  44. adam/commands/devices/__init__.py +0 -0
  45. adam/commands/devices/device.py +27 -0
  46. adam/commands/devices/device_app.py +146 -0
  47. adam/commands/devices/device_auit_log.py +43 -0
  48. adam/commands/devices/device_cass.py +145 -0
  49. adam/commands/devices/device_export.py +86 -0
  50. adam/commands/devices/device_postgres.py +109 -0
  51. adam/commands/devices/devices.py +25 -0
  52. adam/commands/export/__init__.py +0 -0
  53. adam/commands/export/clean_up_export_session.py +53 -0
  54. adam/commands/export/clean_up_export_sessions.py +40 -0
  55. adam/commands/export/drop_export_database.py +58 -0
  56. adam/commands/export/drop_export_databases.py +46 -0
  57. adam/commands/export/export.py +83 -0
  58. adam/commands/export/export_databases.py +170 -0
  59. adam/commands/export/export_select.py +85 -0
  60. adam/commands/export/export_select_x.py +54 -0
  61. adam/commands/export/export_use.py +55 -0
  62. adam/commands/export/exporter.py +364 -0
  63. adam/commands/export/import_session.py +68 -0
  64. adam/commands/export/importer.py +67 -0
  65. adam/commands/export/importer_athena.py +80 -0
  66. adam/commands/export/importer_sqlite.py +47 -0
  67. adam/commands/export/show_column_counts.py +63 -0
  68. adam/commands/export/show_export_databases.py +39 -0
  69. adam/commands/export/show_export_session.py +51 -0
  70. adam/commands/export/show_export_sessions.py +47 -0
  71. adam/commands/export/utils_export.py +291 -0
  72. adam/commands/help.py +12 -7
  73. adam/commands/issues.py +6 -0
  74. adam/commands/kubectl.py +41 -0
  75. adam/commands/login.py +7 -4
  76. adam/commands/logs.py +2 -1
  77. adam/commands/ls.py +4 -107
  78. adam/commands/medusa/medusa.py +2 -26
  79. adam/commands/medusa/medusa_backup.py +2 -2
  80. adam/commands/medusa/medusa_restore.py +3 -4
  81. adam/commands/medusa/medusa_show_backupjobs.py +4 -3
  82. adam/commands/medusa/medusa_show_restorejobs.py +3 -3
  83. adam/commands/nodetool.py +9 -4
  84. adam/commands/param_set.py +1 -1
  85. adam/commands/postgres/postgres.py +42 -43
  86. adam/commands/postgres/{postgres_session.py → postgres_context.py} +43 -42
  87. adam/commands/postgres/postgres_utils.py +31 -0
  88. adam/commands/postgres/psql_completions.py +10 -0
  89. adam/commands/preview_table.py +18 -40
  90. adam/commands/pwd.py +2 -28
  91. adam/commands/reaper/reaper.py +4 -24
  92. adam/commands/reaper/reaper_restart.py +1 -1
  93. adam/commands/reaper/reaper_session.py +2 -2
  94. adam/commands/repair/repair.py +3 -27
  95. adam/commands/repair/repair_log.py +1 -1
  96. adam/commands/repair/repair_run.py +2 -2
  97. adam/commands/repair/repair_scan.py +1 -1
  98. adam/commands/repair/repair_stop.py +1 -1
  99. adam/commands/report.py +6 -0
  100. adam/commands/restart.py +2 -2
  101. adam/commands/rollout.py +1 -1
  102. adam/commands/show/show.py +11 -26
  103. adam/commands/show/show_app_actions.py +3 -0
  104. adam/commands/show/show_app_id.py +1 -1
  105. adam/commands/show/show_app_queues.py +3 -2
  106. adam/commands/show/show_cassandra_status.py +3 -3
  107. adam/commands/show/show_cassandra_version.py +3 -3
  108. adam/commands/show/show_host.py +33 -0
  109. adam/commands/show/show_login.py +3 -0
  110. adam/commands/show/show_processes.py +1 -1
  111. adam/commands/show/show_repairs.py +2 -2
  112. adam/commands/show/show_storage.py +1 -1
  113. adam/commands/watch.py +1 -1
  114. adam/config.py +16 -3
  115. adam/embedded_params.py +1 -1
  116. adam/pod_exec_result.py +10 -2
  117. adam/repl.py +127 -117
  118. adam/repl_commands.py +51 -16
  119. adam/repl_state.py +276 -55
  120. adam/sql/__init__.py +0 -0
  121. adam/sql/sql_completer.py +120 -0
  122. adam/sql/sql_state_machine.py +617 -0
  123. adam/sql/term_completer.py +76 -0
  124. adam/sso/authn_ad.py +1 -1
  125. adam/sso/cred_cache.py +1 -1
  126. adam/sso/idp.py +1 -1
  127. adam/utils.py +83 -2
  128. adam/utils_athena.py +145 -0
  129. adam/utils_audits.py +102 -0
  130. adam/utils_k8s/__init__.py +0 -0
  131. adam/utils_k8s/app_clusters.py +33 -0
  132. adam/utils_k8s/app_pods.py +31 -0
  133. adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -21
  134. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
  135. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  136. adam/{k8s_utils → utils_k8s}/kube_context.py +1 -1
  137. adam/{k8s_utils → utils_k8s}/pods.py +119 -26
  138. adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
  139. adam/{k8s_utils → utils_k8s}/statefulsets.py +5 -4
  140. adam/utils_net.py +24 -0
  141. adam/utils_repl/__init__.py +0 -0
  142. adam/utils_repl/automata_completer.py +48 -0
  143. adam/utils_repl/repl_completer.py +46 -0
  144. adam/utils_repl/state_machine.py +173 -0
  145. adam/utils_sqlite.py +101 -0
  146. adam/version.py +1 -1
  147. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/METADATA +1 -1
  148. kaqing-2.0.145.dist-info/RECORD +227 -0
  149. adam/commands/bash.py +0 -87
  150. adam/commands/cql_utils.py +0 -53
  151. adam/commands/devices.py +0 -89
  152. kaqing-2.0.14.dist-info/RECORD +0 -167
  153. /adam/{k8s_utils → commands/audit}/__init__.py +0 -0
  154. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  155. /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
  156. /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
  157. /adam/{k8s_utils → utils_k8s}/jobs.py +0 -0
  158. /adam/{k8s_utils → utils_k8s}/service_accounts.py +0 -0
  159. /adam/{k8s_utils → utils_k8s}/services.py +0 -0
  160. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  161. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/WHEEL +0 -0
  162. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/entry_points.txt +0 -0
  163. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,63 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.export.export_databases import ExportDatabases
3
+ from adam.config import Config
4
+ from adam.repl_state import ReplState, RequiredState
5
+ from adam.utils import log2
6
+ from adam.utils_athena import Athena
7
+ from adam.utils_sqlite import SQLite
8
+
9
+ class ShowColumnCounts(Command):
10
+ COMMAND = 'show column counts on'
11
+
12
+ # the singleton pattern
13
+ def __new__(cls, *args, **kwargs):
14
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowColumnCounts, 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 ShowColumnCounts.COMMAND
23
+
24
+ def required(self):
25
+ return RequiredState.EXPORT_DB
26
+
27
+ def run(self, cmd: str, state: ReplState):
28
+ if not(args := self.args(cmd)):
29
+ return super().run(cmd, state)
30
+
31
+ state, args = self.apply_state(args, state)
32
+ if not self.validate_state(state):
33
+ return state
34
+
35
+ if not args:
36
+ if state.in_repl:
37
+ log2('Use a SQL statement.')
38
+ else:
39
+ log2('* SQL statement is missing.')
40
+
41
+ Command.display_help()
42
+
43
+ return 'command-missing'
44
+
45
+ copy_or_export = 'copy'
46
+ if state.export_session.startswith('e'):
47
+ copy_or_export = 'export'
48
+
49
+ table = args[0]
50
+ query = Config().get(f'{copy_or_export}.column_counts_query', 'select id, count(id) as columns from {table} group by id')
51
+ query = query.replace('{table}', table)
52
+ ExportDatabases.run_query(query, state.export_session)
53
+
54
+ return state
55
+
56
+ def completion(self, state: ReplState):
57
+ if not state.export_session:
58
+ return {}
59
+
60
+ return super().completion(state, lambda: {t: None for t in ExportDatabases.table_names(state.export_session)})
61
+
62
+ def help(self, _: ReplState):
63
+ return f'{ShowColumnCounts.COMMAND} <export-table-name>\t show column count per id'
@@ -0,0 +1,39 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.devices.device_export import DeviceExport
3
+ from adam.repl_state import ReplState
4
+
5
+ class ShowExportDatabases(Command):
6
+ COMMAND = 'show export databases'
7
+
8
+ # the singleton pattern
9
+ def __new__(cls, *args, **kwargs):
10
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowExportDatabases, cls).__new__(cls)
11
+
12
+ return cls.instance
13
+
14
+ def __init__(self, successor: Command=None):
15
+ super().__init__(successor)
16
+
17
+ def command(self):
18
+ return ShowExportDatabases.COMMAND
19
+
20
+ def required(self):
21
+ return [ReplState.C, ReplState.X]
22
+
23
+ def run(self, cmd: str, state: ReplState):
24
+ if not(args := self.args(cmd)):
25
+ return super().run(cmd, state)
26
+
27
+ state, args = self.apply_state(args, state)
28
+ if not self.validate_state(state):
29
+ return state
30
+
31
+ DeviceExport().show_export_databases()
32
+
33
+ return state
34
+
35
+ def completion(self, state: ReplState):
36
+ return DeviceExport().ls_completion(ShowExportDatabases.COMMAND, state, default = super().completion(state))
37
+
38
+ def help(self, _: ReplState):
39
+ return f'{ShowExportDatabases.COMMAND}\t list export databases'
@@ -0,0 +1,51 @@
1
+ from adam.commands.command import Command
2
+ from adam.commands.export.export_databases import ExportDatabases
3
+ from adam.commands.export.exporter import Exporter
4
+ from adam.repl_state import ReplState, RequiredState
5
+ from adam.utils import log2
6
+
7
+ class ShowExportSession(Command):
8
+ COMMAND = 'show export session'
9
+
10
+ # the singleton pattern
11
+ def __new__(cls, *args, **kwargs):
12
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowExportSession, 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 ShowExportSession.COMMAND
21
+
22
+ def required(self):
23
+ return RequiredState.CLUSTER_OR_POD
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
+ if not args:
34
+ if state.in_repl:
35
+ log2('Specify export database name.')
36
+ else:
37
+ log2('* Database name is missing.')
38
+
39
+ Command.display_help()
40
+
41
+ return 'command-missing'
42
+
43
+ ExportDatabases.disply_export_session(state.sts, state.pod, state.namespace, args[0])
44
+
45
+ return state
46
+
47
+ def completion(self, state: ReplState):
48
+ return super().completion(state, {session: None for session in Exporter.export_session_names(state.sts, state.pod, state.namespace)})
49
+
50
+ def help(self, _: ReplState):
51
+ return f'{ShowExportSession.COMMAND} <export-session-name>\t show export session'
@@ -0,0 +1,47 @@
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 lines_to_tabular, log
5
+ from adam.utils_k8s.statefulsets import StatefulSets
6
+
7
+ class ShowExportSessions(Command):
8
+ COMMAND = 'show export sessions'
9
+
10
+ # the singleton pattern
11
+ def __new__(cls, *args, **kwargs):
12
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowExportSessions, 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 ShowExportSessions.COMMAND
21
+
22
+ def required(self):
23
+ return RequiredState.CLUSTER_OR_POD
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
+ pod = state.pod
34
+ if not pod:
35
+ pod = StatefulSets.pod_names(state.sts, state.namespace)[0]
36
+
37
+ sessions: dict[str, str] = Exporter.find_export_sessions(pod, state.namespace)
38
+ log(lines_to_tabular([f'{session}\t{export_state}' for session, export_state in sorted(sessions.items(), reverse=True)],
39
+ header='EXPORT_SESSION\tSTATUS', separator='\t'))
40
+
41
+ return state
42
+
43
+ def completion(self, state: ReplState):
44
+ return super().completion(state)
45
+
46
+ def help(self, _: ReplState):
47
+ return f'{ShowExportSessions.COMMAND}\t list export sessions'
@@ -0,0 +1,291 @@
1
+ import io
2
+ import re
3
+
4
+ from adam.config import Config
5
+ from adam.pod_exec_result import PodExecResult
6
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
7
+ from adam.utils_k8s.pods import log_prefix
8
+
9
+ class ImportSpec:
10
+ def __init__(self, session: str, importer: str):
11
+ self.session = session
12
+ self.importer = importer
13
+
14
+ def parse_specs(specs_str: str):
15
+ session: str = None
16
+ importer: str = None
17
+
18
+ if specs_str:
19
+ importer, session = ImportSpec._extract_importer(specs_str.strip(' '))
20
+
21
+ return ImportSpec(session, importer)
22
+
23
+ def _extract_importer(spec_str: str) -> tuple[str, str]:
24
+ importer = None
25
+ rest = spec_str
26
+
27
+ p = re.compile(r"(.*?)to\s+(.*)", re.IGNORECASE)
28
+ match = p.match(spec_str)
29
+ if match:
30
+ rest = match.group(1).strip(' ')
31
+ importer = match.group(2).strip(' ')
32
+
33
+ return importer, rest
34
+
35
+ class ExportSpec(ImportSpec):
36
+ def __init__(self, keyspace: str, consistency: str, importer: str, tables: list['ExportTableSpec'], session: str = None):
37
+ super().__init__(None, importer)
38
+
39
+ self.keyspace = keyspace
40
+ self.consistency = consistency
41
+ self.tables = tables
42
+ self.session = session
43
+
44
+ def __str__(self):
45
+ return f'keyspace: {self.keyspace}, consistency: {self.consistency}, importer: {self.importer}, tables: {",".join([t.table for t in self.tables])}, session: {self.session}'
46
+
47
+ def __eq__(self, other):
48
+ if not isinstance(other, ExportSpec):
49
+ return NotImplemented
50
+
51
+ return self.keyspace == other.keyspace and self.tables == other.tables and self.consistency == other.consistency and self.importer == other.importer and self.session == other.session
52
+
53
+ def parse_specs(specs_str: str):
54
+ keyspace: str = None
55
+ consistency: str = None
56
+ importer: str = None
57
+ specs: list[ExportTableSpec] = None
58
+
59
+ if specs_str:
60
+ importer, specs_str = ExportSpec._extract_importer(specs_str.strip(' '))
61
+ keyspace, specs_str = ExportSpec._extract_keyspace(specs_str)
62
+ consistency, specs = ExportSpec._extract_consisteny(specs_str)
63
+
64
+ return ExportSpec(keyspace, consistency, importer, specs)
65
+
66
+ def _extract_keyspace(spec_str: str) -> tuple[str, str]:
67
+ keyspace = None
68
+ rest = spec_str
69
+
70
+ p = re.compile(r"\s*\*\s+in\s+(\S+)(.*)", re.IGNORECASE)
71
+ match = p.match(spec_str)
72
+ if match:
73
+ keyspace = match.group(1).strip(' ')
74
+ rest = match.group(2).strip(' ')
75
+ elif spec_str.startswith('*'):
76
+ keyspace = '*'
77
+ rest = spec_str[1:].strip(' ')
78
+
79
+ return keyspace, rest
80
+
81
+ def _extract_consisteny(spec_str: str) -> tuple[str, list['ExportTableSpec']]:
82
+ consistency = None
83
+
84
+ p = re.compile(r"(.*?)with\s+consistency\s+(.*)", re.IGNORECASE)
85
+ match = p.match(spec_str)
86
+ if match:
87
+ spec_str = match.group(1).strip(' ')
88
+ consistency = match.group(2)
89
+
90
+ if spec_str:
91
+ p = r",\s*(?![^()]*\))"
92
+ specs = re.split(p, spec_str)
93
+
94
+ return consistency, [ExportTableSpec.parse(spec) for spec in specs]
95
+
96
+ return consistency, None
97
+
98
+ class ExportTableSpec:
99
+ def __init__(self, keyspace: str, table: str, columns: str = None, target_table: str = None):
100
+ self.keyspace = keyspace
101
+ self.table = table
102
+ self.columns = columns
103
+ self.target_table = target_table
104
+
105
+ def __str__(self):
106
+ return f'{self.keyspace}.{self.table}({self.columns}) AS {self.target_table}'
107
+
108
+ def __eq__(self, other):
109
+ if not isinstance(other, ExportTableSpec):
110
+ return NotImplemented
111
+
112
+ return self.keyspace == other.keyspace and self.table == other.table and self.columns == other.columns and self.target_table == other.target_table
113
+
114
+ def from_status(status: 'ExportTableStatus'):
115
+ return ExportTableSpec(status.keyspace, status.table, target_table=status.target_table)
116
+
117
+ def parse(spec_str: str) -> 'ExportTableSpec':
118
+ target = None
119
+
120
+ p = re.compile(r"(.*?)\s+as\s+(.*)", re.IGNORECASE)
121
+ match = p.match(spec_str)
122
+ if match:
123
+ spec_str = match.group(1)
124
+ target = match.group(2)
125
+
126
+ keyspace = None
127
+ table = spec_str
128
+ columns = None
129
+
130
+ p = re.compile('(.*?)\.(.*?)\((.*)\)')
131
+ match = p.match(spec_str)
132
+ if match:
133
+ keyspace = match.group(1)
134
+ table = match.group(2)
135
+ columns = match.group(3)
136
+ else:
137
+ p = re.compile('(.*?)\.(.*)')
138
+ match = p.match(spec_str)
139
+ if match:
140
+ keyspace = match.group(1)
141
+ table = match.group(2)
142
+
143
+ return ExportTableSpec(keyspace, table, columns, target)
144
+
145
+ def __eq__(self, other):
146
+ if isinstance(other, ExportTableSpec):
147
+ return self.keyspace == other.keyspace and self.table == other.table and self.columns == other.columns and self.target_table == other.target_table
148
+
149
+ return False
150
+
151
+ def __str__(self):
152
+ return f'{self.keyspace}.{self.table}({self.columns}) as {self.target_table}'
153
+
154
+ class ExportTableStatus:
155
+ def __init__(self, keyspace: str, target_table: str, status: str, table: str = None):
156
+ self.keyspace = keyspace
157
+ self.target_table = target_table
158
+ self.status = status
159
+ self.table = table
160
+
161
+ def __str__(self):
162
+ return f'{self.keyspace}.{self.table} as {self.target_table} = {self.status}'
163
+
164
+ def __eq__(self, other):
165
+ if isinstance(other, ExportTableStatus):
166
+ return self.keyspace == other.keyspace and self.table == other.table and self.status == other.status and self.target_table == other.target_table
167
+
168
+ return False
169
+
170
+ def from_session(sts: str, pod: str, namespace: str, export_session: str):
171
+ statuses: list[ExportTableStatus] = []
172
+
173
+ status_in_whole = 'done'
174
+ log_files: list[str] = find_files(pod, namespace, f'{log_prefix()}-{export_session}_*.log*')
175
+
176
+ for log_file in log_files:
177
+ status: ExportTableStatus = ExportTableStatus.from_log_file(pod, namespace, export_session, log_file)
178
+ statuses.append(status)
179
+
180
+ if status.status != 'done':
181
+ status_in_whole = status.status
182
+
183
+ return statuses, status_in_whole
184
+
185
+ def from_log_file(pod: str, namespace: str, copy_session: str, log_file: str):
186
+ def get_csv_files_n_table(target_table: str):
187
+ db = f'{copy_session}_{target_table}'
188
+ csv_file = f'{csv_dir()}/{db}/*.csv'
189
+ csv_files: list[str] = find_files(pod, namespace, csv_file)
190
+ if csv_files:
191
+ table = target_table
192
+ m = re.match(f'{csv_dir()}/{db}/(.*).csv', csv_files[0])
193
+ if m:
194
+ table = m.group(1)
195
+ return csv_files, table
196
+
197
+ return csv_files, target_table
198
+
199
+ m = re.match(f'{log_prefix()}-{copy_session}_(.*?)\.(.*?)\.log(.*)', log_file)
200
+ if m:
201
+ keyspace = m.group(1)
202
+ target_table = m.group(2)
203
+ state = m.group(3)
204
+ if state == '.pending_import':
205
+ _, table = get_csv_files_n_table(target_table)
206
+ return ExportTableStatus(keyspace, target_table, 'pending_import', table)
207
+ elif state == '.done':
208
+ return ExportTableStatus(keyspace, target_table, 'done', target_table)
209
+
210
+ # 4 rows exported to 1 files in 0 day, 0 hour, 0 minute, and 1.335 seconds.
211
+ pattern = 'rows exported to'
212
+ r: PodExecResult = CassandraNodes.exec(pod, namespace, f"grep '{pattern}' {log_file}", show_out=Config().is_debug(), shell='bash')
213
+ if r.exit_code() == 0:
214
+ csv_files, table = get_csv_files_n_table(target_table)
215
+ if csv_files:
216
+ return ExportTableStatus(keyspace, target_table, 'exported', table)
217
+ else:
218
+ return ExportTableStatus(keyspace, target_table, 'imported', target_table)
219
+ else:
220
+ return ExportTableStatus(keyspace, target_table, 'export_in_pregress')
221
+
222
+ return ExportTableStatus(None, None, 'unknown')
223
+
224
+ def csv_dir():
225
+ return Config().get('export.csv_dir', '/c3/cassandra/tmp')
226
+
227
+ def find_files(pod: str, namespace: str, pattern: str, mmin: int = 0):
228
+ if mmin:
229
+ r = CassandraNodes.exec(pod, namespace, f'find {pattern} -mmin -{mmin}', show_out=Config().is_debug(), shell='bash')
230
+ else:
231
+ r = CassandraNodes.exec(pod, namespace, f'find {pattern}', show_out=Config().is_debug(), shell='bash')
232
+
233
+ log_files = []
234
+ for line in r.stdout.split('\n'):
235
+ line = line.strip(' \r')
236
+ if line:
237
+ log_files.append(line)
238
+
239
+ return log_files
240
+
241
+ class GeneratorStream(io.RawIOBase):
242
+ def __init__(self, generator):
243
+ self._generator = generator
244
+ self._buffer = b'' # Buffer to store leftover bytes from generator yields
245
+
246
+ def readable(self):
247
+ return True
248
+
249
+ def _read_from_generator(self):
250
+ try:
251
+ chunk = next(self._generator)
252
+ if isinstance(chunk, str):
253
+ chunk = chunk.encode('utf-8') # Encode if generator yields strings
254
+ self._buffer += chunk
255
+ except StopIteration:
256
+ pass # Generator exhausted
257
+
258
+ def readinto(self, b):
259
+ # Fill the buffer if necessary
260
+ while len(self._buffer) < len(b):
261
+ old_buffer_len = len(self._buffer)
262
+ self._read_from_generator()
263
+ if len(self._buffer) == old_buffer_len: # Generator exhausted and buffer empty
264
+ break
265
+
266
+ bytes_to_read = min(len(b), len(self._buffer))
267
+ b[:bytes_to_read] = self._buffer[:bytes_to_read]
268
+ self._buffer = self._buffer[bytes_to_read:]
269
+ return bytes_to_read
270
+
271
+ def read(self, size=-1):
272
+ if size == -1: # Read all remaining data
273
+ while True:
274
+ old_buffer_len = len(self._buffer)
275
+ self._read_from_generator()
276
+ if len(self._buffer) == old_buffer_len:
277
+ break
278
+ data = self._buffer
279
+ self._buffer = b''
280
+ return data
281
+ else:
282
+ # Ensure enough data in buffer
283
+ while len(self._buffer) < size:
284
+ old_buffer_len = len(self._buffer)
285
+ self._read_from_generator()
286
+ if len(self._buffer) == old_buffer_len:
287
+ break
288
+
289
+ data = self._buffer[:size]
290
+ self._buffer = self._buffer[size:]
291
+ return data
adam/commands/help.py CHANGED
@@ -23,20 +23,25 @@ class Help(Command):
23
23
  return super().run(cmd, state)
24
24
 
25
25
  def section(cmds : list[ReplCommands]):
26
- return [f' {c.help(state)}' for c in cmds if c.help(state)]
26
+ sorted_cmds = sorted(cmds, key=lambda cmd: cmd.command())
27
+ return [f' {c.help(state)}' for c in sorted_cmds if c.help(state)]
27
28
 
28
29
  lines = []
29
30
  lines.append('NAVIGATION')
30
- lines.append(' a: | c: | p:\t switch to another operational device: App, Cassandra or Postgres')
31
+ lines.append(' a: | c: | l: | p: | x:\t switch to another operational device: App, Cassandra, Audit, Postgres or Export')
31
32
  lines.extend(section(ReplCommands.navigation()))
32
- lines.append('CHECK CASSANDRA')
33
- lines.extend(section(ReplCommands.cassandra_check()))
34
- lines.append('CASSANDRA OPERATIONS')
33
+ lines.append('CASSANDRA')
35
34
  lines.extend(section(ReplCommands.cassandra_ops()))
35
+ lines.append('POSTGRES')
36
+ lines.extend(section(ReplCommands.postgres_ops()))
37
+ lines.append('APP')
38
+ lines.extend(section(ReplCommands.app_ops()))
39
+ lines.append('EXPORT DB')
40
+ lines.extend(section(ReplCommands.export_ops()))
41
+ lines.append('AUDIT')
42
+ lines.extend(section(ReplCommands.audit_ops()))
36
43
  lines.append('TOOLS')
37
44
  lines.extend(section(ReplCommands.tools()))
38
- lines.append('APP')
39
- lines.extend(section(ReplCommands.app()))
40
45
  lines.append('')
41
46
  lines.extend(section(ReplCommands.exit()))
42
47
 
adam/commands/issues.py CHANGED
@@ -21,11 +21,17 @@ class Issues(Command):
21
21
  def command(self):
22
22
  return Issues.COMMAND
23
23
 
24
+ def required(self):
25
+ return ReplState.NON_L
26
+
24
27
  def run(self, cmd: str, state: ReplState):
25
28
  if not(args := self.args(cmd)):
26
29
  return super().run(cmd, state)
27
30
 
28
31
  state, args = self.apply_state(args, state)
32
+ if not self.validate_state(state):
33
+ return state
34
+
29
35
  args, show = Command.extract_options(args, ['-s', '--show'])
30
36
 
31
37
  results = run_checks(state.sts, state.namespace, state.pod, show_output=show)
@@ -0,0 +1,41 @@
1
+ import subprocess
2
+
3
+ from adam.commands.command import Command
4
+ from adam.repl_state import ReplState, RequiredState
5
+
6
+ class Kubectl(Command):
7
+ COMMAND = 'k'
8
+
9
+ # the singleton pattern
10
+ def __new__(cls, *args, **kwargs):
11
+ if not hasattr(cls, 'instance'): cls.instance = super(Kubectl, 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 Kubectl.COMMAND
20
+
21
+ def required(self):
22
+ return RequiredState.NAMESPACE
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
+ subprocess.run(["kubectl"] + args)
33
+
34
+ return state
35
+
36
+ def completion(self, state: ReplState):
37
+ return super().completion(state)
38
+
39
+
40
+ def help(self, _: ReplState):
41
+ return f'{Kubectl.COMMAND} \t run a kubectl command'
adam/commands/login.py CHANGED
@@ -8,7 +8,7 @@ from adam.config import Config
8
8
  from adam.sso.idp import Idp
9
9
  from adam.sso.idp_login import IdpLogin
10
10
  from adam.commands.command import Command
11
- from adam.repl_state import ReplState
11
+ from adam.repl_state import ReplState, RequiredState
12
12
  from adam.utils import log, log2
13
13
 
14
14
  class Login(Command):
@@ -26,6 +26,9 @@ class Login(Command):
26
26
  def command(self):
27
27
  return Login.COMMAND
28
28
 
29
+ def required(self):
30
+ return ReplState.NON_L
31
+
29
32
  def run(self, cmd: str, state: ReplState):
30
33
  def custom_handler(signum, frame):
31
34
  AppSession.ctrl_c_entered = True
@@ -38,7 +41,7 @@ class Login(Command):
38
41
  state, args = self.apply_state(args, state)
39
42
  args, debug = Command.extract_options(args, ['d'])
40
43
  if debug:
41
- Config().set('debug.show-out', True)
44
+ Config().set('debug', True)
42
45
 
43
46
  username: str = os.getenv('USERNAME')
44
47
  if len(args) > 0:
@@ -59,8 +62,8 @@ class Login(Command):
59
62
 
60
63
  return state
61
64
 
62
- def completion(self, _: ReplState):
63
- return {}
65
+ def completion(self, state: ReplState):
66
+ return super().completion(state)
64
67
 
65
68
  def help(self, _: ReplState):
66
69
  return f'{Login.COMMAND}\t SSO login'
adam/commands/logs.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from adam.commands.command import Command
2
2
  from adam.config import Config
3
- from adam.k8s_utils.cassandra_nodes import CassandraNodes
3
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
4
4
  from adam.repl_state import ReplState, RequiredState
5
5
 
6
6
  class Logs(Command):
@@ -33,6 +33,7 @@ class Logs(Command):
33
33
  return CassandraNodes.exec(state.pod, state.namespace, f'cat {path}')
34
34
 
35
35
  def completion(self, _: ReplState):
36
+ # available only on cli
36
37
  return {}
37
38
 
38
39
  def help(self, _: ReplState):