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
@@ -0,0 +1,48 @@
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
+ with self.validate(args, state) as (args, state):
30
+ if not args:
31
+ if state.in_repl:
32
+ log2('Specify export database name.')
33
+ else:
34
+ log2('* Database name is missing.')
35
+
36
+ Command.display_help()
37
+
38
+ return 'command-missing'
39
+
40
+ ExportDatabases.disply_export_session(state.sts, state.pod, state.namespace, args[0])
41
+
42
+ return state
43
+
44
+ def completion(self, state: ReplState):
45
+ return super().completion(state, {session: None for session in Exporter.export_session_names(state.sts, state.pod, state.namespace)})
46
+
47
+ def help(self, _: ReplState):
48
+ return f'{ShowExportSession.COMMAND} <export-session-name>\t show export session'
@@ -0,0 +1,44 @@
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
+ with self.validate(args, state) as (args, state):
30
+ pod = state.pod
31
+ if not pod:
32
+ pod = StatefulSets.pod_names(state.sts, state.namespace)[0]
33
+
34
+ sessions: dict[str, str] = Exporter.find_export_sessions(pod, state.namespace)
35
+ log(lines_to_tabular([f'{session}\t{export_state}' for session, export_state in sorted(sessions.items(), reverse=True)],
36
+ header='EXPORT_SESSION\tSTATUS', separator='\t'))
37
+
38
+ return state
39
+
40
+ def completion(self, state: ReplState):
41
+ return super().completion(state)
42
+
43
+ def help(self, _: ReplState):
44
+ return f'{ShowExportSessions.COMMAND}\t list export sessions'
@@ -0,0 +1,314 @@
1
+ import io
2
+ import re
3
+
4
+ from adam.config import Config
5
+ from adam.pod_exec_result import PodExecResult
6
+ from adam.repl_state import ReplState
7
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
8
+ from adam.utils_k8s.pods import log_prefix
9
+ from adam.utils_k8s.statefulsets import StatefulSets
10
+
11
+ class ImportSpec:
12
+ def __init__(self, session: str, importer: str):
13
+ self.session = session
14
+ self.importer = importer
15
+
16
+ def parse_specs(specs_str: str):
17
+ session: str = None
18
+ importer: str = None
19
+
20
+ if specs_str:
21
+ importer, session = ImportSpec._extract_importer(specs_str.strip(' '))
22
+
23
+ return ImportSpec(session, importer)
24
+
25
+ def _extract_importer(spec_str: str) -> tuple[str, str]:
26
+ importer = None
27
+ rest = spec_str
28
+
29
+ p = re.compile(r"(.*?)to\s+(.*)", re.IGNORECASE)
30
+ match = p.match(spec_str)
31
+ if match:
32
+ rest = match.group(1).strip(' ')
33
+ importer = match.group(2).strip(' ')
34
+
35
+ return importer, rest
36
+
37
+ class ExportSpec(ImportSpec):
38
+ def __init__(self, keyspace: str, consistency: str, importer: str, tables: list['ExportTableSpec'], session: str = None):
39
+ super().__init__(None, importer)
40
+
41
+ self.keyspace = keyspace
42
+ self.consistency = consistency
43
+ self.tables = tables
44
+ self.session = session
45
+
46
+ def __str__(self):
47
+ return f'keyspace: {self.keyspace}, consistency: {self.consistency}, importer: {self.importer}, tables: {",".join([t.table for t in self.tables])}, session: {self.session}'
48
+
49
+ def __eq__(self, other):
50
+ if not isinstance(other, ExportSpec):
51
+ return NotImplemented
52
+
53
+ 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
54
+
55
+ def parse_specs(specs_str: str):
56
+ keyspace: str = None
57
+ consistency: str = None
58
+ importer: str = None
59
+ specs: list[ExportTableSpec] = None
60
+
61
+ if specs_str:
62
+ importer, specs_str = ExportSpec._extract_importer(specs_str.strip(' '))
63
+ keyspace, specs_str = ExportSpec._extract_keyspace(specs_str)
64
+ consistency, specs = ExportSpec._extract_consisteny(specs_str)
65
+
66
+ return ExportSpec(keyspace, consistency, importer, specs)
67
+
68
+ def _extract_keyspace(spec_str: str) -> tuple[str, str]:
69
+ keyspace = None
70
+ rest = spec_str
71
+
72
+ p = re.compile(r"\s*\*\s+in\s+(\S+)(.*)", re.IGNORECASE)
73
+ match = p.match(spec_str)
74
+ if match:
75
+ keyspace = match.group(1).strip(' ')
76
+ rest = match.group(2).strip(' ')
77
+ elif spec_str.startswith('*'):
78
+ keyspace = '*'
79
+ rest = spec_str[1:].strip(' ')
80
+
81
+ return keyspace, rest
82
+
83
+ def _extract_consisteny(spec_str: str) -> tuple[str, list['ExportTableSpec']]:
84
+ consistency = None
85
+
86
+ p = re.compile(r"(.*?)with\s+consistency\s+(.*)", re.IGNORECASE)
87
+ match = p.match(spec_str)
88
+ if match:
89
+ spec_str = match.group(1).strip(' ')
90
+ consistency = match.group(2)
91
+
92
+ if spec_str:
93
+ p = r",\s*(?![^()]*\))"
94
+ specs = re.split(p, spec_str)
95
+
96
+ return consistency, [ExportTableSpec.parse(spec) for spec in specs]
97
+
98
+ return consistency, None
99
+
100
+ class ExportTableSpec:
101
+ def __init__(self, keyspace: str, table: str, columns: str = None, target_table: str = None):
102
+ self.keyspace = keyspace
103
+ self.table = table
104
+ self.columns = columns
105
+ self.target_table = target_table
106
+
107
+ def __str__(self):
108
+ return f'{self.keyspace}.{self.table}({self.columns}) AS {self.target_table}'
109
+
110
+ def __eq__(self, other):
111
+ if not isinstance(other, ExportTableSpec):
112
+ return NotImplemented
113
+
114
+ return self.keyspace == other.keyspace and self.table == other.table and self.columns == other.columns and self.target_table == other.target_table
115
+
116
+ def from_status(status: 'ExportTableStatus'):
117
+ return ExportTableSpec(status.keyspace, status.table, target_table=status.target_table)
118
+
119
+ def parse(spec_str: str) -> 'ExportTableSpec':
120
+ target = None
121
+
122
+ p = re.compile(r"(.*?)\s+as\s+(.*)", re.IGNORECASE)
123
+ match = p.match(spec_str)
124
+ if match:
125
+ spec_str = match.group(1)
126
+ target = match.group(2)
127
+
128
+ keyspace = None
129
+ table = spec_str
130
+ columns = None
131
+
132
+ p = re.compile('(.*?)\.(.*?)\((.*)\)')
133
+ match = p.match(spec_str)
134
+ if match:
135
+ keyspace = match.group(1)
136
+ table = match.group(2)
137
+ columns = match.group(3)
138
+ else:
139
+ p = re.compile('(.*?)\.(.*)')
140
+ match = p.match(spec_str)
141
+ if match:
142
+ keyspace = match.group(1)
143
+ table = match.group(2)
144
+
145
+ return ExportTableSpec(keyspace, table, columns, target)
146
+
147
+ def __eq__(self, other):
148
+ if isinstance(other, ExportTableSpec):
149
+ return self.keyspace == other.keyspace and self.table == other.table and self.columns == other.columns and self.target_table == other.target_table
150
+
151
+ return False
152
+
153
+ def __str__(self):
154
+ return f'{self.keyspace}.{self.table}({self.columns}) as {self.target_table}'
155
+
156
+ class ExportTableStatus:
157
+ def __init__(self, keyspace: str, target_table: str, status: str, table: str = None):
158
+ self.keyspace = keyspace
159
+ self.target_table = target_table
160
+ self.status = status
161
+ self.table = table
162
+
163
+ def __str__(self):
164
+ return f'{self.keyspace}.{self.table} as {self.target_table} = {self.status}'
165
+
166
+ def __eq__(self, other):
167
+ if isinstance(other, ExportTableStatus):
168
+ return self.keyspace == other.keyspace and self.table == other.table and self.status == other.status and self.target_table == other.target_table
169
+
170
+ return False
171
+
172
+ def from_session(sts: str, pod: str, namespace: str, export_session: str):
173
+ statuses: list[ExportTableStatus] = []
174
+
175
+ status_in_whole = 'done'
176
+ log_files: list[str] = find_files(pod, namespace, f'{log_prefix()}-{export_session}_*.log*')
177
+
178
+ for log_file in log_files:
179
+ status: ExportTableStatus = ExportTableStatus.from_log_file(pod, namespace, export_session, log_file)
180
+ statuses.append(status)
181
+
182
+ if status.status != 'done':
183
+ status_in_whole = status.status
184
+
185
+ return statuses, status_in_whole
186
+
187
+ def from_log_file(pod: str, namespace: str, copy_session: str, log_file: str):
188
+ def get_csv_files_n_table(target_table: str):
189
+ db = f'{copy_session}_{target_table}'
190
+ csv_file = f'{csv_dir()}/{db}/*.csv'
191
+ csv_files: list[str] = find_files(pod, namespace, csv_file)
192
+ if csv_files:
193
+ table = target_table
194
+ m = re.match(f'{csv_dir()}/{db}/(.*).csv', csv_files[0])
195
+ if m:
196
+ table = m.group(1)
197
+ return csv_files, table
198
+
199
+ return csv_files, target_table
200
+
201
+ m = re.match(f'{log_prefix()}-{copy_session}_(.*?)\.(.*?)\.log(.*)', log_file)
202
+ if m:
203
+ keyspace = m.group(1)
204
+ target_table = m.group(2)
205
+ state = m.group(3)
206
+ if state == '.pending_import':
207
+ _, table = get_csv_files_n_table(target_table)
208
+ return ExportTableStatus(keyspace, target_table, 'pending_import', table)
209
+ elif state == '.done':
210
+ return ExportTableStatus(keyspace, target_table, 'done', target_table)
211
+
212
+ # 4 rows exported to 1 files in 0 day, 0 hour, 0 minute, and 1.335 seconds.
213
+ pattern = 'rows exported to'
214
+ r: PodExecResult = CassandraNodes.exec(pod, namespace, f"grep '{pattern}' {log_file}", show_out=Config().is_debug(), shell='bash')
215
+ if r.exit_code() == 0:
216
+ csv_files, table = get_csv_files_n_table(target_table)
217
+ if csv_files:
218
+ return ExportTableStatus(keyspace, target_table, 'exported', table)
219
+ else:
220
+ return ExportTableStatus(keyspace, target_table, 'imported', target_table)
221
+ else:
222
+ return ExportTableStatus(keyspace, target_table, 'export_in_pregress')
223
+
224
+ return ExportTableStatus(None, None, 'unknown')
225
+
226
+ def csv_dir():
227
+ return Config().get('export.csv_dir', '/c3/cassandra/tmp')
228
+
229
+ def find_files(pod: str, namespace: str, pattern: str, mmin: int = 0):
230
+ if mmin:
231
+ r = CassandraNodes.exec(pod, namespace, f'find {pattern} -mmin -{mmin}', show_out=Config().is_debug(), shell='bash')
232
+ else:
233
+ r = CassandraNodes.exec(pod, namespace, f'find {pattern}', show_out=Config().is_debug(), shell='bash')
234
+
235
+ log_files = []
236
+ for line in r.stdout.split('\n'):
237
+ line = line.strip(' \r')
238
+ if line:
239
+ log_files.append(line)
240
+
241
+ return log_files
242
+
243
+ class GeneratorStream(io.RawIOBase):
244
+ def __init__(self, generator):
245
+ self._generator = generator
246
+ self._buffer = b'' # Buffer to store leftover bytes from generator yields
247
+
248
+ def readable(self):
249
+ return True
250
+
251
+ def _read_from_generator(self):
252
+ try:
253
+ chunk = next(self._generator)
254
+ if isinstance(chunk, str):
255
+ chunk = chunk.encode('utf-8') # Encode if generator yields strings
256
+ self._buffer += chunk
257
+ except StopIteration:
258
+ pass # Generator exhausted
259
+
260
+ def readinto(self, b):
261
+ # Fill the buffer if necessary
262
+ while len(self._buffer) < len(b):
263
+ old_buffer_len = len(self._buffer)
264
+ self._read_from_generator()
265
+ if len(self._buffer) == old_buffer_len: # Generator exhausted and buffer empty
266
+ break
267
+
268
+ bytes_to_read = min(len(b), len(self._buffer))
269
+ b[:bytes_to_read] = self._buffer[:bytes_to_read]
270
+ self._buffer = self._buffer[bytes_to_read:]
271
+ return bytes_to_read
272
+
273
+ def read(self, size=-1):
274
+ if size == -1: # Read all remaining data
275
+ while True:
276
+ old_buffer_len = len(self._buffer)
277
+ self._read_from_generator()
278
+ if len(self._buffer) == old_buffer_len:
279
+ break
280
+ data = self._buffer
281
+ self._buffer = b''
282
+ return data
283
+ else:
284
+ # Ensure enough data in buffer
285
+ while len(self._buffer) < size:
286
+ old_buffer_len = len(self._buffer)
287
+ self._read_from_generator()
288
+ if len(self._buffer) == old_buffer_len:
289
+ break
290
+
291
+ data = self._buffer[:size]
292
+ self._buffer = self._buffer[size:]
293
+ return data
294
+
295
+ class PodHandler:
296
+ def __init__(self, state: ReplState):
297
+ self.state = state
298
+
299
+ def __enter__(self):
300
+ state = self.state
301
+
302
+ if not state.pod:
303
+ state.push()
304
+ state.pod = StatefulSets.pod_names(state.sts, state.namespace)[0]
305
+
306
+ return state
307
+
308
+ def __exit__(self, exc_type, exc_val, exc_tb):
309
+ self.state.pop()
310
+
311
+ return False
312
+
313
+ def state_with_pod(state: ReplState):
314
+ return PodHandler(state)
adam/commands/help.py CHANGED
@@ -28,16 +28,20 @@ class Help(Command):
28
28
 
29
29
  lines = []
30
30
  lines.append('NAVIGATION')
31
- lines.append(' a: | c: | l: | p:\t switch to another operational device: App, Cassandra, Audit or Postgres')
31
+ lines.append(' a: | c: | l: | p: | x:\t switch to another operational device: App, Cassandra, Audit, Postgres or Export')
32
32
  lines.extend(section(ReplCommands.navigation()))
33
- lines.append('CHECK CASSANDRA')
34
- lines.extend(section(ReplCommands.cassandra_check()))
35
- lines.append('CASSANDRA OPERATIONS')
33
+ lines.append('CASSANDRA')
36
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()))
37
43
  lines.append('TOOLS')
38
44
  lines.extend(section(ReplCommands.tools()))
39
- lines.append('APP')
40
- lines.extend(section(ReplCommands.app()))
41
45
  lines.append('')
42
46
  lines.extend(section(ReplCommands.exit()))
43
47
 
@@ -0,0 +1,49 @@
1
+ from abc import abstractmethod
2
+
3
+ from adam.commands.command import Command
4
+ from adam.commands.command_helpers import ClusterCommandHelper
5
+ from adam.repl_state import ReplState
6
+ from adam.utils import lines_to_tabular, log, log2
7
+
8
+ class IntermediateCommand(Command):
9
+ def run(self, cmd: str, state: ReplState):
10
+ if not(args := self.args(cmd)):
11
+ return super().run(cmd, state)
12
+
13
+ return self.intermediate_run(cmd, state, args, self.cmd_list())
14
+
15
+ @abstractmethod
16
+ def cmd_list(self):
17
+ pass
18
+
19
+ def intermediate_run(self, cmd: str, state: ReplState, args: list[str], cmds: list['Command'], separator='\t', display_help=True):
20
+ state, _ = self.apply_state(args, state)
21
+
22
+ if state.in_repl:
23
+ if display_help:
24
+ log(lines_to_tabular([c.help(state) for c in cmds], separator=separator))
25
+
26
+ return 'command-missing'
27
+ else:
28
+ # head with the Chain of Responsibility pattern
29
+ if not self.run_subcommand(cmd, state):
30
+ if display_help:
31
+ log2('* Command is missing.')
32
+ Command.display_help()
33
+ return 'command-missing'
34
+
35
+ return state
36
+
37
+ def run_subcommand(self, cmd: str, state: ReplState):
38
+ cmds = Command.chain(self.cmd_list())
39
+ return cmds.run(cmd, state)
40
+
41
+ def intermediate_help(super_help: str, cmd: str, cmd_list: list['Command'], separator='\t', show_cluster_help=False):
42
+ log(super_help)
43
+ log()
44
+ log('Sub-Commands:')
45
+
46
+ log(lines_to_tabular([c.help(ReplState()).replace(f'{cmd} ', ' ', 1) for c in cmd_list], separator=separator))
47
+ if show_cluster_help:
48
+ log()
49
+ ClusterCommandHelper.cluster_help()
adam/commands/issues.py CHANGED
@@ -1,10 +1,9 @@
1
1
  from adam.checks.check_result import CheckResult
2
2
  from adam.checks.check_utils import run_checks
3
- from adam.checks.issue import Issue
3
+ from adam.commands import extract_options
4
4
  from adam.commands.command import Command
5
- from adam.repl_session import ReplSession
6
5
  from adam.repl_state import ReplState
7
- from adam.utils import lines_to_tabular, log, log2
6
+ from adam.utils_issues import IssuesUtils
8
7
 
9
8
  class Issues(Command):
10
9
  COMMAND = 'issues'
@@ -21,49 +20,24 @@ class Issues(Command):
21
20
  def command(self):
22
21
  return Issues.COMMAND
23
22
 
23
+ def required(self):
24
+ return ReplState.NON_L
25
+
24
26
  def run(self, cmd: str, state: ReplState):
25
27
  if not(args := self.args(cmd)):
26
28
  return super().run(cmd, state)
27
29
 
28
- state, args = self.apply_state(args, state)
29
- args, show = Command.extract_options(args, ['-s', '--show'])
30
-
31
- results = run_checks(state.sts, state.namespace, state.pod, show_output=show)
32
-
33
- issues = CheckResult.collect_issues(results)
34
- Issues.show_issues(issues, in_repl=state.in_repl)
35
-
36
- return issues if issues else 'issues'
37
-
38
- def show(check_results: list[CheckResult], in_repl = False):
39
- Issues.show_issues(CheckResult.collect_issues(check_results), in_repl=in_repl)
30
+ with self.validate(args, state) as (args, state):
31
+ with extract_options(args, ['-s', '--show']) as (args, show_out):
32
+ results = run_checks(state.sts, state.namespace, state.pod, show_out=show_out)
40
33
 
41
- def show_issues(issues: list[Issue], in_repl = False):
42
- if not issues:
43
- log2('No issues found.')
44
- else:
45
- suggested = 0
46
- log2(f'* {len(issues)} issues found.')
47
- lines = []
48
- for i, issue in enumerate(issues, start=1):
49
- lines.append(f"{i}||{issue.category}||{issue.desc}")
50
- lines.append(f"||statefulset||{issue.statefulset}@{issue.namespace}")
51
- lines.append(f"||pod||{issue.pod}@{issue.namespace}")
52
- if issue.details:
53
- lines.append(f"||details||{issue.details}")
34
+ issues = CheckResult.collect_issues(results)
35
+ IssuesUtils.show_issues(issues, in_repl=state.in_repl)
54
36
 
55
- if issue.suggestion:
56
- lines.append(f'||suggestion||{issue.suggestion}')
57
- if in_repl:
58
- ReplSession().prompt_session.history.append_string(issue.suggestion)
59
- suggested += 1
60
- log(lines_to_tabular(lines, separator='||'))
61
- if suggested:
62
- log2()
63
- log2(f'* {suggested} suggested commands are added to history. Press <Up> arrow to access them.')
37
+ return issues if issues else 'issues'
64
38
 
65
- def completion(self, _: ReplState):
66
- return {Issues.COMMAND: None}
39
+ def completion(self, state: ReplState):
40
+ return super().completion(state, {'-s': None})
67
41
 
68
42
  def help(self, _: ReplState):
69
- return f'{Issues.COMMAND}\t find all issues'
43
+ return f'{Issues.COMMAND} [-s]\t find all issues'
@@ -0,0 +1,38 @@
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
+ with self.validate(args, state) as (args, state):
29
+ subprocess.run(["kubectl"] + args)
30
+
31
+ return state
32
+
33
+ def completion(self, state: ReplState):
34
+ return super().completion(state)
35
+
36
+
37
+ def help(self, _: ReplState):
38
+ return f'{Kubectl.COMMAND} \t run a kubectl command'
adam/commands/login.py CHANGED
@@ -4,11 +4,12 @@ import traceback
4
4
 
5
5
  from adam.app_session import AppSession
6
6
  from adam.apps import Apps
7
+ from adam.commands import extract_options
7
8
  from adam.config import Config
8
9
  from adam.sso.idp import Idp
9
10
  from adam.sso.idp_login import IdpLogin
10
11
  from adam.commands.command import Command
11
- from adam.repl_state import ReplState
12
+ from adam.repl_state import ReplState, RequiredState
12
13
  from adam.utils import log, log2
13
14
 
14
15
  class Login(Command):
@@ -26,41 +27,44 @@ class Login(Command):
26
27
  def command(self):
27
28
  return Login.COMMAND
28
29
 
30
+ def required(self):
31
+ return ReplState.NON_L
32
+
29
33
  def run(self, cmd: str, state: ReplState):
34
+ if not(args := self.args(cmd)):
35
+ return super().run(cmd, state)
36
+
30
37
  def custom_handler(signum, frame):
31
38
  AppSession.ctrl_c_entered = True
32
39
 
33
40
  signal.signal(signal.SIGINT, custom_handler)
34
41
 
35
- if not(args := self.args(cmd)):
36
- return super().run(cmd, state)
37
-
38
- state, args = self.apply_state(args, state)
39
- args, debug = Command.extract_options(args, ['d'])
40
- if debug:
41
- Config().set('debug', True)
42
+ with self.validate(args, state) as (args, state):
43
+ with extract_options(args, 'd') as (args, debug):
44
+ if debug:
45
+ Config().set('debug', True)
42
46
 
43
- username: str = os.getenv('USERNAME')
44
- if len(args) > 0:
45
- username = args[0]
47
+ username: str = os.getenv('USERNAME')
48
+ if len(args) > 0:
49
+ username = args[0]
46
50
 
47
- login: IdpLogin = None
48
- try:
49
- if not(host := Apps.app_host('c3', 'c3', state.namespace)):
50
- log2('Cannot locate ingress for app.')
51
- return state
51
+ login: IdpLogin = None
52
+ try:
53
+ if not(host := Apps.app_host('c3', 'c3', state.namespace)):
54
+ log2('Cannot locate ingress for app.')
55
+ return state
52
56
 
53
- if not (login := Idp.login(host, username=username, use_token_from_env=False)):
54
- log2('Invalid username/password. Please try again.')
55
- except:
56
- log2(traceback.format_exc())
57
+ if not (login := Idp.login(host, username=username, use_token_from_env=False)):
58
+ log2('Invalid username/password. Please try again.')
59
+ except:
60
+ log2(traceback.format_exc())
57
61
 
58
- log(f'IDP_TOKEN={login.ser() if login else ""}')
62
+ log(f'IDP_TOKEN={login.ser() if login else ""}')
59
63
 
60
- return state
64
+ return state
61
65
 
62
- def completion(self, _: ReplState):
63
- return {}
66
+ def completion(self, state: ReplState):
67
+ return super().completion(state)
64
68
 
65
69
  def help(self, _: ReplState):
66
70
  return f'{Login.COMMAND}\t SSO login'