kaqing 2.0.184__py3-none-any.whl → 2.0.227__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 (205) hide show
  1. adam/app_session.py +1 -1
  2. adam/batch.py +15 -15
  3. adam/checks/compactionstats.py +2 -1
  4. adam/checks/cpu.py +2 -1
  5. adam/checks/disk.py +6 -5
  6. adam/checks/gossip.py +2 -1
  7. adam/checks/memory.py +2 -1
  8. adam/checks/status.py +2 -1
  9. adam/commands/app/app.py +4 -4
  10. adam/commands/app/app_ping.py +2 -2
  11. adam/commands/{login.py → app/login.py} +2 -2
  12. adam/commands/app/show_app_actions.py +3 -3
  13. adam/commands/app/show_app_id.py +2 -2
  14. adam/commands/app/show_app_queues.py +2 -2
  15. adam/commands/{show → app}/show_login.py +3 -3
  16. adam/commands/app/utils_app.py +9 -1
  17. adam/commands/audit/audit.py +8 -24
  18. adam/commands/audit/audit_repair_tables.py +3 -3
  19. adam/commands/audit/audit_run.py +3 -3
  20. adam/commands/audit/completions_l.py +15 -0
  21. adam/commands/audit/show_last10.py +2 -3
  22. adam/commands/audit/show_slow10.py +2 -2
  23. adam/commands/audit/show_top10.py +2 -2
  24. adam/commands/bash/bash.py +3 -3
  25. adam/commands/bash/utils_bash.py +1 -1
  26. adam/commands/cassandra/download_cassandra_log.py +45 -0
  27. adam/commands/cassandra/restart_cluster.py +47 -0
  28. adam/commands/cassandra/restart_node.py +51 -0
  29. adam/commands/cassandra/restart_nodes.py +47 -0
  30. adam/commands/{rollout.py → cassandra/rollout.py} +3 -3
  31. adam/commands/{show → cassandra}/show_cassandra_repairs.py +7 -5
  32. adam/commands/{show → cassandra}/show_cassandra_status.py +24 -17
  33. adam/commands/{show → cassandra}/show_cassandra_version.py +2 -2
  34. adam/commands/cassandra/show_processes.py +50 -0
  35. adam/commands/cassandra/show_storage.py +44 -0
  36. adam/commands/{watch.py → cassandra/watch.py} +2 -2
  37. adam/commands/cli/__init__.py +0 -0
  38. adam/commands/{cli_commands.py → cli/cli_commands.py} +6 -1
  39. adam/commands/{clipboard_copy.py → cli/clipboard_copy.py} +4 -4
  40. adam/commands/{show/show_commands.py → cli/show_cli_commands.py} +5 -5
  41. adam/commands/code.py +2 -2
  42. adam/commands/command.py +54 -14
  43. adam/commands/commands_utils.py +14 -6
  44. adam/commands/config/__init__.py +0 -0
  45. adam/commands/{param_get.py → config/param_get.py} +2 -2
  46. adam/commands/{param_set.py → config/param_set.py} +2 -2
  47. adam/commands/{show → config}/show_params.py +3 -3
  48. adam/commands/{alter_tables.py → cql/alter_tables.py} +3 -3
  49. adam/commands/cql/completions_c.py +29 -0
  50. adam/commands/cql/cqlsh.py +4 -8
  51. adam/commands/cql/utils_cql.py +36 -17
  52. adam/commands/debug/__init__.py +0 -0
  53. adam/commands/debug/debug.py +22 -0
  54. adam/commands/debug/debug_completes.py +35 -0
  55. adam/commands/debug/debug_timings.py +35 -0
  56. adam/commands/debug/show_offloaded_completes.py +45 -0
  57. adam/commands/deploy/code_start.py +2 -2
  58. adam/commands/deploy/code_stop.py +2 -2
  59. adam/commands/deploy/deploy_frontend.py +2 -2
  60. adam/commands/deploy/deploy_pg_agent.py +2 -2
  61. adam/commands/deploy/deploy_pod.py +2 -2
  62. adam/commands/deploy/undeploy_frontend.py +2 -2
  63. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  64. adam/commands/deploy/undeploy_pod.py +2 -2
  65. adam/commands/devices/device.py +37 -11
  66. adam/commands/devices/device_app.py +7 -7
  67. adam/commands/devices/device_auit_log.py +2 -2
  68. adam/commands/devices/device_cass.py +6 -6
  69. adam/commands/devices/device_export.py +7 -4
  70. adam/commands/devices/device_postgres.py +19 -9
  71. adam/commands/devices/devices.py +1 -1
  72. adam/commands/diag/__init__.py +0 -0
  73. adam/commands/{check.py → diag/check.py} +3 -3
  74. adam/commands/diag/generate_report.py +52 -0
  75. adam/commands/{issues.py → diag/issues.py} +3 -2
  76. adam/commands/exit.py +2 -2
  77. adam/commands/export/clean_up_all_export_sessions.py +2 -2
  78. adam/commands/export/clean_up_export_sessions.py +2 -2
  79. adam/commands/export/completions_x.py +11 -0
  80. adam/commands/export/download_export_session.py +5 -5
  81. adam/commands/export/drop_export_database.py +2 -2
  82. adam/commands/export/drop_export_databases.py +2 -2
  83. adam/commands/export/export.py +3 -19
  84. adam/commands/export/export_databases.py +20 -11
  85. adam/commands/export/export_select.py +9 -34
  86. adam/commands/export/export_sessions.py +13 -11
  87. adam/commands/export/export_use.py +6 -6
  88. adam/commands/export/export_x_select.py +48 -0
  89. adam/commands/export/exporter.py +140 -53
  90. adam/commands/export/import_files.py +3 -7
  91. adam/commands/export/import_session.py +2 -6
  92. adam/commands/export/importer.py +12 -13
  93. adam/commands/export/importer_athena.py +15 -35
  94. adam/commands/export/importer_sqlite.py +19 -8
  95. adam/commands/export/show_column_counts.py +11 -12
  96. adam/commands/export/show_export_databases.py +4 -4
  97. adam/commands/export/show_export_session.py +5 -5
  98. adam/commands/export/show_export_sessions.py +4 -4
  99. adam/commands/export/utils_export.py +40 -25
  100. adam/commands/fs/__init__.py +0 -0
  101. adam/commands/{cat.py → fs/cat.py} +4 -4
  102. adam/commands/fs/cat_local.py +42 -0
  103. adam/commands/{cd.py → fs/cd.py} +4 -4
  104. adam/commands/{download_file.py → fs/download_file.py} +7 -7
  105. adam/commands/{find_files.py → fs/find_files.py} +7 -7
  106. adam/commands/{find_processes.py → fs/find_processes.py} +14 -22
  107. adam/commands/{head.py → fs/head.py} +5 -5
  108. adam/commands/fs/head_local.py +46 -0
  109. adam/commands/{ls.py → fs/ls.py} +4 -4
  110. adam/commands/fs/ls_local.py +40 -0
  111. adam/commands/{pwd.py → fs/pwd.py} +2 -2
  112. adam/commands/fs/rm.py +18 -0
  113. adam/commands/fs/rm_downloads.py +39 -0
  114. adam/commands/fs/rm_logs.py +44 -0
  115. adam/commands/fs/rm_logs_local.py +38 -0
  116. adam/commands/{shell.py → fs/shell.py} +2 -2
  117. adam/commands/{show → fs}/show_adam.py +3 -3
  118. adam/commands/{show → fs}/show_host.py +2 -2
  119. adam/commands/fs/show_last_results.py +39 -0
  120. adam/commands/fs/tail.py +36 -0
  121. adam/commands/fs/tail_local.py +46 -0
  122. adam/commands/fs/utils_fs.py +192 -0
  123. adam/commands/help.py +2 -2
  124. adam/commands/intermediate_command.py +3 -0
  125. adam/commands/kubectl.py +2 -2
  126. adam/commands/medusa/medusa_backup.py +2 -2
  127. adam/commands/medusa/medusa_restore.py +4 -18
  128. adam/commands/medusa/medusa_show_backupjobs.py +2 -2
  129. adam/commands/medusa/medusa_show_restorejobs.py +2 -2
  130. adam/commands/medusa/utils_medusa.py +15 -0
  131. adam/commands/nodetool/__init__.py +0 -0
  132. adam/commands/nodetool/nodetool.py +87 -0
  133. adam/commands/nodetool/utils_nodetool.py +44 -0
  134. adam/commands/postgres/completions_p.py +22 -0
  135. adam/commands/postgres/postgres.py +10 -20
  136. adam/commands/postgres/postgres_databases.py +3 -3
  137. adam/commands/postgres/postgres_ls.py +3 -3
  138. adam/commands/postgres/postgres_preview.py +2 -2
  139. adam/commands/postgres/utils_postgres.py +12 -2
  140. adam/commands/preview_table.py +3 -4
  141. adam/commands/reaper/reaper_forward.py +2 -2
  142. adam/commands/reaper/reaper_forward_stop.py +2 -2
  143. adam/commands/reaper/reaper_restart.py +2 -2
  144. adam/commands/reaper/reaper_run_abort.py +2 -2
  145. adam/commands/reaper/reaper_runs.py +14 -12
  146. adam/commands/reaper/reaper_runs_abort.py +2 -2
  147. adam/commands/reaper/reaper_schedule_activate.py +8 -4
  148. adam/commands/reaper/reaper_schedule_start.py +3 -4
  149. adam/commands/reaper/reaper_schedule_stop.py +3 -4
  150. adam/commands/reaper/reaper_schedules.py +2 -2
  151. adam/commands/reaper/reaper_status.py +2 -2
  152. adam/commands/reaper/utils_reaper.py +41 -6
  153. adam/commands/repair/repair_log.py +2 -2
  154. adam/commands/repair/repair_run.py +2 -2
  155. adam/commands/repair/repair_scan.py +2 -4
  156. adam/commands/repair/repair_stop.py +2 -3
  157. adam/commands/{show/show.py → show.py} +12 -11
  158. adam/config.py +4 -5
  159. adam/embedded_params.py +1 -1
  160. adam/repl.py +24 -10
  161. adam/repl_commands.py +68 -45
  162. adam/repl_session.py +16 -1
  163. adam/repl_state.py +16 -1
  164. adam/sql/async_executor.py +62 -0
  165. adam/sql/lark_completer.py +286 -0
  166. adam/sql/lark_parser.py +604 -0
  167. adam/sql/qingl.lark +1075 -0
  168. adam/sso/cred_cache.py +2 -5
  169. adam/utils.py +259 -82
  170. adam/utils_async_job.py +73 -0
  171. adam/utils_k8s/app_clusters.py +11 -4
  172. adam/utils_k8s/app_pods.py +10 -5
  173. adam/utils_k8s/cassandra_clusters.py +19 -7
  174. adam/utils_k8s/cassandra_nodes.py +16 -6
  175. adam/utils_k8s/k8s.py +9 -0
  176. adam/utils_k8s/kube_context.py +1 -4
  177. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +8 -2
  178. adam/utils_k8s/pods.py +189 -29
  179. adam/utils_k8s/statefulsets.py +5 -2
  180. adam/utils_local.py +78 -2
  181. adam/utils_repl/appendable_completer.py +6 -0
  182. adam/utils_repl/repl_completer.py +51 -4
  183. adam/utils_sqlite.py +3 -8
  184. adam/version.py +1 -1
  185. {kaqing-2.0.184.dist-info → kaqing-2.0.227.dist-info}/METADATA +1 -1
  186. kaqing-2.0.227.dist-info/RECORD +280 -0
  187. kaqing-2.0.227.dist-info/top_level.txt +2 -0
  188. teddy/__init__.py +0 -0
  189. teddy/lark_parser.py +436 -0
  190. teddy/lark_parser2.py +618 -0
  191. adam/commands/cql/cql_completions.py +0 -32
  192. adam/commands/export/export_select_x.py +0 -54
  193. adam/commands/logs.py +0 -37
  194. adam/commands/nodetool.py +0 -69
  195. adam/commands/postgres/psql_completions.py +0 -11
  196. adam/commands/report.py +0 -61
  197. adam/commands/restart.py +0 -60
  198. adam/commands/show/show_processes.py +0 -49
  199. adam/commands/show/show_storage.py +0 -42
  200. kaqing-2.0.184.dist-info/RECORD +0 -244
  201. kaqing-2.0.184.dist-info/top_level.txt +0 -1
  202. /adam/commands/{show → cassandra}/__init__.py +0 -0
  203. /adam/commands/{nodetool_commands.py → nodetool/nodetool_commands.py} +0 -0
  204. {kaqing-2.0.184.dist-info → kaqing-2.0.227.dist-info}/WHEEL +0 -0
  205. {kaqing-2.0.184.dist-info → kaqing-2.0.227.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,73 @@
1
+ from datetime import datetime
2
+ import re
3
+ import traceback
4
+
5
+ from adam.utils import log_dir
6
+
7
+ class AsyncJobs:
8
+ _last_id: str = None
9
+ _last_command: str = None
10
+
11
+ def log_file(command: str, pod_name: str = None, job_id: str = None, pod_suffix: str = None, err = False, dir: str = None):
12
+ try:
13
+ if not job_id:
14
+ job_id = AsyncJobs.new_id()
15
+
16
+ AsyncJobs._last_id = job_id
17
+ AsyncJobs._last_command = command
18
+
19
+ AsyncJobs.write_last_command(job_id, command)
20
+
21
+ cmd_name = ''
22
+ if command.startswith('nodetool '):
23
+ command = command.strip(' &')
24
+ cmd_name = f".{'_'.join(command.split(' ')[5:])}"
25
+
26
+ if pod_suffix is None:
27
+ pod_suffix = '{pod}'
28
+ if pod_name:
29
+ pod_suffix = pod_name
30
+ if groups := re.match(r'.*-(.*)', pod_name):
31
+ pod_suffix = f'-{groups[1]}'
32
+
33
+ if not dir:
34
+ dir = log_dir()
35
+
36
+ return f'{dir}/{job_id}{cmd_name}{pod_suffix}.{"err" if err else "log"}'
37
+ except:
38
+ traceback.print_exc()
39
+
40
+ def new_id(dt: datetime = None):
41
+ if not dt:
42
+ dt = datetime.now()
43
+
44
+ id = dt.strftime("%d%H%M%S")
45
+ AsyncJobs._last_id = id
46
+
47
+ return id
48
+
49
+ def last_command():
50
+ last_id = AsyncJobs._last_id
51
+ last_command = AsyncJobs._last_command
52
+
53
+ if not last_id or not last_command:
54
+ l0, l1 = AsyncJobs.read_last_command()
55
+ if not last_id:
56
+ last_id = l0
57
+ if not last_command:
58
+ last_command = l1
59
+
60
+ return last_id, last_command
61
+
62
+ def write_last_command(job_id: str, command: str):
63
+ with open(f'{log_dir()}/last', 'wt') as f:
64
+ f.write(job_id)
65
+ f.write('\n')
66
+ f.write(command)
67
+
68
+ def read_last_command():
69
+ with open(f'{log_dir()}/last', 'rt') as f:
70
+ job_id = f.readline().strip(' \r\n')
71
+ command = f.readline().strip(' \r\n')
72
+
73
+ return job_id, command
@@ -2,7 +2,7 @@ import sys
2
2
  from typing import TypeVar
3
3
 
4
4
  from adam.utils_k8s.app_pods import AppPods
5
- from adam.pod_exec_result import PodExecResult
5
+ from adam.utils_k8s.pod_exec_result import PodExecResult
6
6
  from adam.utils import log, log2
7
7
  from adam.utils_k8s.pods import Pods
8
8
  from .kube_context import KubeContext
@@ -11,8 +11,15 @@ T = TypeVar('T')
11
11
 
12
12
  # utility collection on app clusters; methods are all static
13
13
  class AppClusters:
14
- def exec(pods: list[str], namespace: str, command: str, action: str = 'action',
15
- max_workers=0, show_out=True, on_any = False, shell = '/bin/sh', backgrounded = False) -> list[PodExecResult]:
14
+ def exec(pods: list[str],
15
+ namespace: str,
16
+ command: str,
17
+ action: str = 'action',
18
+ max_workers=0,
19
+ show_out=True,
20
+ on_any = False,
21
+ shell = '/bin/sh',
22
+ backgrounded = False) -> list[PodExecResult]:
16
23
  samples = 1 if on_any else sys.maxsize
17
24
  msg = 'd`Running|Ran ' + action + ' command onto {size} pods'
18
25
  with Pods.parallelize(pods, max_workers, samples, msg, action=action) as exec:
@@ -23,6 +30,6 @@ class AppClusters:
23
30
  if result.stdout:
24
31
  log(result.stdout)
25
32
  if result.stderr:
26
- log2(result.stderr, file=sys.stderr)
33
+ log2(result.stderr)
27
34
 
28
35
  return results
@@ -4,7 +4,7 @@ from kubernetes import client
4
4
 
5
5
  from adam.config import Config
6
6
  from adam.utils_k8s.pods import Pods
7
- from adam.pod_exec_result import PodExecResult
7
+ from adam.utils_k8s.pod_exec_result import PodExecResult
8
8
  from adam.repl_session import ReplSession
9
9
 
10
10
  # utility collection on app pods; methods are all static
@@ -25,12 +25,17 @@ class AppPods:
25
25
 
26
26
  return v1.list_namespaced_pod(namespace, label_selector=label_selector).items
27
27
 
28
- def exec(pod_name: str, namespace: str, command: str, show_out = True, throw_err = False, shell = '/bin/sh', backgrounded = False) -> PodExecResult:
28
+ def exec(pod_name: str,
29
+ namespace: str,
30
+ command: str,
31
+ show_out = True,
32
+ throw_err = False,
33
+ shell = '/bin/sh',
34
+ backgrounded = False) -> PodExecResult:
29
35
  container = Config().get('app.container-name', 'c3-server')
30
36
  r = Pods.exec(pod_name, container, namespace, command, show_out = show_out, throw_err = throw_err, shell = shell, backgrounded = backgrounded)
31
37
 
32
- if r and Config().get('repl.history.push-cat-remote-log-file', True):
33
- if r.log_file and ReplSession().prompt_session:
34
- ReplSession().prompt_session.history.append_string(f'@{r.pod} cat {r.log_file}')
38
+ if r and r.log_file:
39
+ ReplSession().append_history(f':cat {r.log_file}')
35
40
 
36
41
  return r
@@ -2,9 +2,10 @@ import sys
2
2
  from typing import TypeVar
3
3
 
4
4
  from adam.config import Config
5
+ from adam.utils_async_job import AsyncJobs
5
6
  from adam.utils_k8s.cassandra_nodes import CassandraNodes
6
- from adam.pod_exec_result import PodExecResult
7
- from adam.utils import log, log2
7
+ from adam.utils_k8s.pod_exec_result import PodExecResult
8
+ from adam.utils import log, log2, log_to_pods, pod_log_dir
8
9
  from adam.utils_k8s.pods import Pods
9
10
  from adam.utils_k8s.statefulsets import StatefulSets
10
11
 
@@ -21,20 +22,31 @@ class CassandraClusters:
21
22
  on_any = False,
22
23
  shell = '/bin/sh',
23
24
  backgrounded = False,
24
- log_file = None) -> list[PodExecResult]:
25
+ log_file = None,
26
+ history=True,
27
+ text_color: str = None) -> list[PodExecResult]:
25
28
 
26
29
  pods = StatefulSets.pod_names(sts, namespace)
27
30
  samples = 1 if on_any else sys.maxsize
31
+ if (backgrounded or command.endswith(' &')) and not log_file:
32
+ pod_suffix = None
33
+ dir = None
34
+ if log_to_pods():
35
+ pod_suffix = ''
36
+ dir = pod_log_dir()
37
+
38
+ log_file = AsyncJobs.log_file(command, job_id=AsyncJobs.new_id(), pod_suffix=pod_suffix, dir=dir)
39
+
28
40
  msg = 'd`Running|Ran ' + action + ' command onto {size} pods'
29
41
  with Pods.parallelize(pods, max_workers, samples, msg, action=action) as exec:
30
- results: list[PodExecResult] = exec.map(lambda pod: CassandraNodes.exec(pod, namespace, command, False, False, shell, backgrounded, log_file))
42
+ results: list[PodExecResult] = exec.map(lambda pod: CassandraNodes.exec(pod, namespace, command, False, False, shell, backgrounded, log_file, history, text_color))
31
43
  for result in results:
32
44
  if show_out and not Config().is_debug():
33
- log(result.command)
45
+ log(result.command, text_color=text_color)
34
46
  if result.stdout:
35
- log(result.stdout)
47
+ log(result.stdout, text_color=text_color)
36
48
  if result.stderr:
37
- log2(result.stderr, file=sys.stderr)
49
+ log2(result.stderr, text_color=text_color)
38
50
 
39
51
  return results
40
52
 
@@ -1,17 +1,27 @@
1
1
  from adam.config import Config
2
2
  from adam.utils_k8s.pods import Pods
3
3
  from adam.utils_k8s.secrets import Secrets
4
- from adam.pod_exec_result import PodExecResult
4
+ from adam.utils_k8s.pod_exec_result import PodExecResult
5
5
  from adam.repl_session import ReplSession
6
6
 
7
7
  # utility collection on cassandra nodes; methods are all static
8
8
  class CassandraNodes:
9
- def exec(pod_name: str, namespace: str, command: str, show_out = True, throw_err = False, shell = '/bin/sh', backgrounded = False, log_file = None) -> PodExecResult:
10
- r = Pods.exec(pod_name, "cassandra", namespace, command, show_out = show_out, throw_err = throw_err, shell = shell, backgrounded = backgrounded, log_file=log_file)
9
+ def exec(pod_name: str,
10
+ namespace: str,
11
+ command: str,
12
+ show_out = True,
13
+ throw_err = False,
14
+ shell = '/bin/sh',
15
+ backgrounded = False,
16
+ log_file = None,
17
+ history = True,
18
+ text_color: str = None) -> PodExecResult:
19
+ r: PodExecResult = Pods.exec(pod_name, "cassandra", namespace, command, show_out = show_out, throw_err = throw_err, shell = shell, backgrounded = backgrounded, log_file=log_file, text_color=text_color)
11
20
 
12
- if r and Config().get('repl.history.push-cat-remote-log-file', True):
13
- if r.log_file and ReplSession().prompt_session:
14
- ReplSession().prompt_session.history.append_string(f'@{r.pod} cat {r.log_file}')
21
+ if history and r and r.log_file:
22
+ # entry = f':tail {r.log_file}'
23
+
24
+ ReplSession().append_history(r.log_file)
15
25
 
16
26
  return r
17
27
 
adam/utils_k8s/k8s.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Callable
2
+ import inspect
2
3
  import re
3
4
  import portforward
4
5
 
@@ -49,6 +50,14 @@ class PortForwardHandler:
49
50
  raise InvalidStateException(state)
50
51
 
51
52
  self.pod = pod
53
+
54
+ # pf = portforward.forward(state.namespace, pod, self.local_port + 1, self.target_port, log_level=portforward.LogLevel.DEBUG)
55
+ # print(inspect.getsource(pf.__enter__))
56
+ # print('test portforward START', state.namespace, pod, self.local_port + 1, self.target_port, pf.__enter__)
57
+ # with pf:
58
+ # print('test portforward BODY')
59
+ # print('test portforward OK')
60
+
52
61
  self.forward_connection = portforward.forward(state.namespace, pod, self.local_port, self.target_port)
53
62
  if self.inc_connection_cnt() == 1:
54
63
  self.forward_connection.__enter__()
@@ -102,7 +102,4 @@ class KubeContext:
102
102
  return name if re.match(r"^(?!pg-).*-k8spg-.*$", name) else None
103
103
 
104
104
  def show_out(s: bool):
105
- return s or Config().is_debug()
106
-
107
- def show_parallelism():
108
- return Config().get('debugs.show-parallelism', False)
105
+ return s or Config().is_debug()
@@ -1,8 +1,8 @@
1
1
  import yaml
2
2
 
3
- from adam.utils import log_exc
3
+ from adam.utils import ExecResult, log_exc
4
4
 
5
- class PodExecResult:
5
+ class PodExecResult(ExecResult):
6
6
  # {
7
7
  # 'metadata': {},
8
8
  # 'status': 'Failure',
@@ -34,6 +34,12 @@ class PodExecResult:
34
34
 
35
35
  return code
36
36
 
37
+ def cat_log_file_cmd(self):
38
+ if self.pod and self.log_file:
39
+ return f'@{self.pod} cat {self.log_file}'
40
+
41
+ return None
42
+
37
43
  def __str__(self):
38
44
  return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
39
45
 
adam/utils_k8s/pods.py CHANGED
@@ -1,18 +1,22 @@
1
1
  from collections.abc import Callable
2
- from datetime import datetime
3
2
  import os
3
+ import re
4
+ import subprocess
4
5
  import sys
5
6
  import time
6
7
  from typing import TypeVar
7
8
  from kubernetes import client
8
9
  from kubernetes.stream import stream
9
10
  from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
11
+ from prompt_toolkit import print_formatted_text, HTML
10
12
 
11
13
  from adam.config import Config
14
+ from adam.repl_session import ReplSession
12
15
  from adam.utils_k8s.volumes import ConfigMapMount
13
- from adam.pod_exec_result import PodExecResult
14
- from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_exc
15
- from adam.utils_local import local_tmp_dir
16
+ from adam.utils_k8s.pod_exec_result import PodExecResult
17
+ from adam.utils import GeneratorStream, ParallelMapHandler, PodLogFile, log2, debug, log_exc, log_to_pods, pod_log_dir
18
+ from adam.utils_local import local_downloads_dir, local_exec
19
+ from adam.utils_async_job import AsyncJobs
16
20
  from .kube_context import KubeContext
17
21
 
18
22
  from websocket._core import WebSocket
@@ -48,7 +52,7 @@ class Pods:
48
52
  if samples == sys.maxsize:
49
53
  samples = Config().action_node_samples(action, sys.maxsize)
50
54
 
51
- return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
55
+ return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg, name=action)
52
56
 
53
57
  def exec(pod_name: str,
54
58
  container: str,
@@ -60,12 +64,19 @@ class Pods:
60
64
  backgrounded = False,
61
65
  log_file = None,
62
66
  interaction: Callable[[any, list[str]], any] = None,
63
- env_prefix: str = None):
67
+ env_prefix: str = None,
68
+ text_color: str = None):
64
69
  if _TEST_POD_EXEC_OUTS:
65
70
  return _TEST_POD_EXEC_OUTS
66
71
 
67
72
  show_out = KubeContext.show_out(show_out)
68
73
 
74
+ if backgrounded or command.endswith(' &'):
75
+ if log_to_pods():
76
+ return Pods.exec_backgrounded_logging_to_pod(pod_name, container, namespace, command, show_out, shell, log_file, env_prefix, text_color)
77
+ else:
78
+ return Pods.exec_backgrounded(pod_name, container, command, show_out, shell, log_file, env_prefix, text_color)
79
+
69
80
  api = client.CoreV1Api()
70
81
 
71
82
  tty = True
@@ -73,25 +84,28 @@ class Pods:
73
84
  if env_prefix:
74
85
  exec_command = [shell, '-c', f'{env_prefix} {command}']
75
86
 
76
- if backgrounded or command.endswith(' &'):
77
- # should be false for starting a background process
78
- tty = False
79
-
80
- if Config().get('repl.background-process.auto-nohup', True):
81
- command = command.strip(' &')
82
- cmd_name = ''
83
- if command.startswith('nodetool '):
84
- cmd_name = f".{'_'.join(command.split(' ')[5:])}"
85
-
86
- if not log_file:
87
- log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
88
- command = f"nohup {command} > {log_file} 2>&1 &"
89
- if env_prefix:
90
- command = f'{env_prefix} {command}'
91
- exec_command = [shell, '-c', command]
87
+ # if backgrounded or command.endswith(' &'):
88
+ # # should be false for starting a background process
89
+ # tty = False
90
+
91
+ # if Config().get('repl.background-process.auto-nohup', True):
92
+ # command = command.strip(' &')
93
+ # cmd_name = ''
94
+ # if command.startswith('nodetool '):
95
+ # cmd_name = f".{'_'.join(command.split(' ')[5:])}"
96
+
97
+ # if not log_file:
98
+ # log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
99
+ # command = f"nohup {command} > {log_file} 2>&1 &"
100
+ # if env_prefix:
101
+ # command = f'{env_prefix} {command}'
102
+ # exec_command = [shell, '-c', command]
92
103
 
93
104
  k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
94
- debug(k_command)
105
+ if Config().is_debug():
106
+ debug(k_command)
107
+ elif show_out:
108
+ log2(k_command, text_color=text_color)
95
109
 
96
110
  resp: WSClient = stream(
97
111
  api.connect_get_namespaced_pod_exec,
@@ -116,14 +130,22 @@ class Pods:
116
130
  if resp.peek_stdout():
117
131
  frag = resp.read_stdout()
118
132
  stdout.append(frag)
119
- if show_out: print(frag, end="")
133
+ if show_out:
134
+ if text_color:
135
+ print_formatted_text(HTML(f'<ansi{text_color}>{frag}</ansi{text_color}>'), end="")
136
+ else:
137
+ print(frag, end="")
120
138
 
121
139
  if interaction:
122
140
  interaction(resp, stdout)
123
141
  if resp.peek_stderr():
124
142
  frag = resp.read_stderr()
125
143
  stderr.append(frag)
126
- if show_out: print(frag, end="")
144
+ if show_out:
145
+ if text_color:
146
+ print_formatted_text(HTML(f'<ansi{text_color}>{frag}</ansi{text_color}>'), end="")
147
+ else:
148
+ print(frag, end="")
127
149
 
128
150
  with log_exc():
129
151
  # get the exit code from server
@@ -132,7 +154,7 @@ class Pods:
132
154
  if throw_err:
133
155
  raise e
134
156
  else:
135
- log2(e)
157
+ log2(e, text_color=text_color)
136
158
  finally:
137
159
  resp.close()
138
160
  if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
@@ -141,6 +163,96 @@ class Pods:
141
163
 
142
164
  return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
143
165
 
166
+ def exec_backgrounded(pod_name: str,
167
+ container: str,
168
+ command: str,
169
+ show_out = True,
170
+ shell = '/bin/sh',
171
+ log_file = None,
172
+ env_prefix: str = None,
173
+ text_color: str = None):
174
+ # nohup kubectl exec cs-a7b13e29bd-cs-a7b13e29bd-default-sts-0 -c cassandra -- /bin/sh -c "nohup nodetool -u cs-a7b13e29bd-superuser -pw ... repair > /tmp/qing-db/q/logs/19230320.repair-0.log 2> /tmp/qing-db/q/logs/19230320.repair-0.err &" &
175
+
176
+ command = command.strip(' &')
177
+
178
+ log_pod_file = None
179
+ if log_file:
180
+ log_pod_file = Pods.log_file_from_template(log_file, pod_name=pod_name)
181
+ else:
182
+ log_pod_file = AsyncJobs.log_file(command, pod_name=pod_name)
183
+
184
+ if env_prefix:
185
+ command = f'{env_prefix} {command}'
186
+
187
+ log_err_file = log_pod_file.replace('.log', '.err')
188
+
189
+ command = command.replace('"', '\\"')
190
+ # nohup kubectl exec cs-a7b13e29bd-cs-a7b13e29bd-default-sts-0 -c cassandra -- /bin/sh -c "nodetool -u cs-a7b13e29bd-superuser -pw ... repair &"
191
+ # > /tmp/qing-db/q/logs/19080002.repair-0.log 2> /tmp/qing-db/q/logs/19080002.repair-0.err &
192
+ cmd = f'nohup kubectl exec {pod_name} -c {container} -- {shell} -c "{command} &" > {log_pod_file} 2> {log_err_file}'
193
+ cmd = f'{cmd} &'
194
+
195
+ if show_out:
196
+ log2(cmd, text_color=text_color)
197
+
198
+ result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
199
+ return PodExecResult(result.stdout, result.stderr, cmd, None, pod=pod_name, log_file=log_pod_file)
200
+
201
+ def exec_backgrounded_logging_to_pod(pod_name: str,
202
+ container: str,
203
+ namespace: str,
204
+ command: str,
205
+ show_out = True,
206
+ shell = '/bin/sh',
207
+ log_file = None,
208
+ env_prefix: str = None,
209
+ text_color: str = None):
210
+ command = command.strip(' &')
211
+
212
+ log_pod_file = None
213
+ if log_file:
214
+ log_pod_file = log_file
215
+ else:
216
+ pod_suffix = None
217
+ dir = None
218
+ if log_to_pods():
219
+ pod_suffix = ''
220
+ dir = pod_log_dir()
221
+
222
+ log_pod_file = AsyncJobs.log_file(command, job_id=AsyncJobs.new_id(), pod_suffix=pod_suffix, dir=dir)
223
+
224
+ if env_prefix:
225
+ command = f'{env_prefix} {command}'
226
+
227
+ log_err_file = log_pod_file.replace('.log', '.err')
228
+
229
+ command = command.replace('"', '\\"')
230
+ # nohup kubectl exec sts-0 -c cassandra -- /bin/sh -c "nohup nodetool -u cs-superuser -pw ... repair > /tmp/qing-db/q/logs/19230320.repair-0.log 2> /tmp/qing-db/q/logs/19230320.repair-0.err &" &
231
+ cmd = f'nohup kubectl exec {pod_name} -c {container} -- {shell} -c "nohup {command} > {log_pod_file} 2> {log_err_file} &"'
232
+ cmd = f'{cmd} &'
233
+
234
+ if show_out:
235
+ log2(cmd, text_color=text_color)
236
+
237
+ result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
238
+ return PodExecResult(result.stdout, result.stderr, cmd, None, pod=pod_name, log_file=PodLogFile(log_pod_file, pod=pod_name))
239
+
240
+ def log_file_from_template(log_file: str, pod_name: str):
241
+ if not log_file:
242
+ return None
243
+
244
+ pod_suffix = pod_name
245
+ if pod_name:
246
+ if groups := re.match(r'.*-(.*)', pod_name):
247
+ pod_suffix = f'-{groups[1]}'
248
+ elif groups := re.match(r'.*_(.*)', pod_name):
249
+ pod_suffix = f'-{groups[1]}'
250
+
251
+ if not pod_suffix.startswith('-'):
252
+ pod_suffix = f'-{pod_suffix}'
253
+
254
+ return log_file.replace('{pod}', pod_suffix)
255
+
144
256
  def read_file(pod_name: str, container: str, namespace: str, file_path: str):
145
257
  v1 = client.CoreV1Api()
146
258
 
@@ -175,13 +287,15 @@ class Pods:
175
287
 
176
288
  def download_file(pod_name: str, container: str, namespace: str, from_path: str, to_path: str = None):
177
289
  if not to_path:
178
- to_path = f'{local_tmp_dir()}/{os.path.basename(from_path)}'
290
+ to_path = f'{local_downloads_dir()}/{os.path.basename(from_path)}'
179
291
 
180
292
  bytes = Pods.read_file(pod_name, container, namespace, from_path)
181
293
  with open(to_path, 'wb') as f:
182
294
  for item in GeneratorStream(bytes):
183
295
  f.write(item)
184
296
 
297
+ ReplSession().append_history(f':cat {to_path}')
298
+
185
299
  return to_path
186
300
 
187
301
  def get_container(namespace: str, pod_name: str, container_name: str):
@@ -294,5 +408,51 @@ class Pods:
294
408
  def completed(namespace: str, pod_name: str):
295
409
  return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
296
410
 
297
- def log_prefix():
298
- return Config().get('log-prefix', '/tmp/qing')
411
+ _dirs_created = set()
412
+
413
+ def creating_dir(pod_name: str,
414
+ container: str,
415
+ namespace: str,
416
+ dir: str,
417
+ show_out = False):
418
+ key = f'{dir}@{pod_name}'
419
+ if key not in Pods._dirs_created:
420
+ Pods._dirs_created.add(key)
421
+ Pods.exec(pod_name, container, namespace, f'mkdir -p {dir}', show_out=show_out, shell='bash')
422
+
423
+ return dir
424
+
425
+ def find_files(pod: str, container: str, namespace: str, pattern: str, mmin: int = 0, remote = False):
426
+ stdout = ''
427
+ if not remote:
428
+ # find . -maxdepth 1 -type f -name '*'
429
+ dir = os.path.dirname(pattern)
430
+ base = os.path.basename(pattern)
431
+ cmd = ['find', dir, '-name', base]
432
+ if mmin:
433
+ cmd += ['-mmin', f'-{mmin}']
434
+
435
+ cmd += ["-exec", "stat", "-c", "'%n %s'", "{}", "\;"]
436
+
437
+ stdout = local_exec(cmd, show_out=Config().is_debug()).stdout
438
+ else:
439
+ cmd = f'find {pattern}'
440
+ if mmin:
441
+ cmd = f'{cmd} -mmin -{mmin}'
442
+
443
+ cmd += " -exec stat -c '%n %s' {} \;"
444
+
445
+ stdout = Pods.exec(pod, container, namespace, cmd, show_out=Config().is_debug(), shell='bash').stdout
446
+
447
+ log_files: list[PodLogFile] = []
448
+ for line in stdout.split('\n'):
449
+ line = line.strip(' \r')
450
+ if line:
451
+ tokens = line.split(' ')
452
+ log_files.append(PodLogFile(tokens[0], pod, size=tokens[1]))
453
+
454
+ return log_files
455
+
456
+ def file_size(pod: str, container:str, namespace: str, file: str):
457
+ stdout = Pods.exec(pod, container, namespace, f'stat -c %s {file}', show_out=Config().is_debug(), shell='bash').stdout
458
+ return stdout.strip('\r\n ')
@@ -56,8 +56,11 @@ class StatefulSets:
56
56
  return statefulset_pods
57
57
 
58
58
  @functools.lru_cache()
59
- def pod_names(ss: str, ns: str):
60
- return [pod.metadata.name for pod in StatefulSets.pods(ss, ns)]
59
+ def pod_names(sts: str, ns: str):
60
+ if not sts:
61
+ return []
62
+
63
+ return [pod.metadata.name for pod in StatefulSets.pods(sts, ns)]
61
64
 
62
65
  def restarted_at(ss: str, ns: str):
63
66
  # returns timestamp and if being rolled out