kaqing 2.0.98__py3-none-any.whl → 2.0.171__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -7
  3. adam/batch.py +4 -18
  4. adam/checks/check_utils.py +14 -46
  5. adam/checks/cpu.py +7 -1
  6. adam/checks/cpu_metrics.py +52 -0
  7. adam/columns/columns.py +3 -1
  8. adam/columns/cpu.py +3 -1
  9. adam/columns/cpu_metrics.py +22 -0
  10. adam/commands/__init__.py +15 -0
  11. adam/commands/alter_tables.py +50 -61
  12. adam/commands/app_cmd.py +38 -0
  13. adam/commands/app_ping.py +8 -14
  14. adam/commands/audit/audit.py +43 -30
  15. adam/commands/audit/audit_repair_tables.py +26 -46
  16. adam/commands/audit/audit_run.py +50 -0
  17. adam/commands/audit/show_last10.py +48 -0
  18. adam/commands/audit/show_slow10.py +47 -0
  19. adam/commands/audit/show_top10.py +45 -0
  20. adam/commands/audit/utils_show_top10.py +59 -0
  21. adam/commands/bash/__init__.py +5 -0
  22. adam/commands/bash/bash.py +36 -0
  23. adam/commands/bash/bash_completer.py +93 -0
  24. adam/commands/bash/utils_bash.py +16 -0
  25. adam/commands/cat.py +50 -0
  26. adam/commands/cd.py +15 -91
  27. adam/commands/check.py +23 -18
  28. adam/commands/cli_commands.py +2 -3
  29. adam/commands/code.py +57 -0
  30. adam/commands/command.py +96 -40
  31. adam/commands/commands_utils.py +9 -19
  32. adam/commands/cp.py +33 -39
  33. adam/commands/cql/cql_completions.py +30 -8
  34. adam/commands/cql/cqlsh.py +12 -27
  35. adam/commands/cql/utils_cql.py +343 -0
  36. adam/commands/deploy/code_start.py +7 -10
  37. adam/commands/deploy/code_stop.py +4 -21
  38. adam/commands/deploy/code_utils.py +3 -3
  39. adam/commands/deploy/deploy.py +4 -21
  40. adam/commands/deploy/deploy_frontend.py +14 -17
  41. adam/commands/deploy/deploy_pg_agent.py +3 -6
  42. adam/commands/deploy/deploy_pod.py +67 -73
  43. adam/commands/deploy/deploy_utils.py +14 -24
  44. adam/commands/deploy/undeploy.py +4 -21
  45. adam/commands/deploy/undeploy_frontend.py +4 -7
  46. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  47. adam/commands/deploy/undeploy_pod.py +11 -12
  48. adam/commands/devices/device.py +118 -0
  49. adam/commands/devices/device_app.py +173 -0
  50. adam/commands/devices/device_auit_log.py +49 -0
  51. adam/commands/devices/device_cass.py +185 -0
  52. adam/commands/devices/device_export.py +86 -0
  53. adam/commands/devices/device_postgres.py +144 -0
  54. adam/commands/devices/devices.py +25 -0
  55. adam/commands/exit.py +1 -4
  56. adam/commands/export/__init__.py +0 -0
  57. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  58. adam/commands/export/clean_up_export_sessions.py +51 -0
  59. adam/commands/export/drop_export_database.py +55 -0
  60. adam/commands/export/drop_export_databases.py +43 -0
  61. adam/commands/export/export.py +53 -0
  62. adam/commands/export/export_databases.py +170 -0
  63. adam/commands/export/export_handlers.py +71 -0
  64. adam/commands/export/export_select.py +81 -0
  65. adam/commands/export/export_select_x.py +54 -0
  66. adam/commands/export/export_use.py +52 -0
  67. adam/commands/export/exporter.py +352 -0
  68. adam/commands/export/import_session.py +40 -0
  69. adam/commands/export/importer.py +67 -0
  70. adam/commands/export/importer_athena.py +80 -0
  71. adam/commands/export/importer_sqlite.py +47 -0
  72. adam/commands/export/show_column_counts.py +54 -0
  73. adam/commands/export/show_export_databases.py +36 -0
  74. adam/commands/export/show_export_session.py +48 -0
  75. adam/commands/export/show_export_sessions.py +44 -0
  76. adam/commands/export/utils_export.py +314 -0
  77. adam/commands/help.py +10 -6
  78. adam/commands/intermediate_command.py +49 -0
  79. adam/commands/issues.py +14 -40
  80. adam/commands/kubectl.py +38 -0
  81. adam/commands/login.py +28 -24
  82. adam/commands/logs.py +4 -6
  83. adam/commands/ls.py +11 -116
  84. adam/commands/medusa/medusa.py +4 -22
  85. adam/commands/medusa/medusa_backup.py +20 -24
  86. adam/commands/medusa/medusa_restore.py +30 -32
  87. adam/commands/medusa/medusa_show_backupjobs.py +16 -17
  88. adam/commands/medusa/medusa_show_restorejobs.py +12 -17
  89. adam/commands/nodetool.py +11 -17
  90. adam/commands/param_get.py +11 -12
  91. adam/commands/param_set.py +9 -10
  92. adam/commands/postgres/postgres.py +43 -36
  93. adam/commands/postgres/{postgres_session.py → postgres_context.py} +80 -46
  94. adam/commands/postgres/postgres_ls.py +4 -8
  95. adam/commands/postgres/postgres_preview.py +5 -9
  96. adam/commands/postgres/psql_completions.py +2 -2
  97. adam/commands/postgres/utils_postgres.py +66 -0
  98. adam/commands/preview_table.py +8 -61
  99. adam/commands/pwd.py +14 -44
  100. adam/commands/reaper/reaper.py +4 -24
  101. adam/commands/reaper/reaper_forward.py +48 -55
  102. adam/commands/reaper/reaper_forward_session.py +6 -0
  103. adam/commands/reaper/reaper_forward_stop.py +10 -16
  104. adam/commands/reaper/reaper_restart.py +7 -14
  105. adam/commands/reaper/reaper_run_abort.py +11 -30
  106. adam/commands/reaper/reaper_runs.py +42 -57
  107. adam/commands/reaper/reaper_runs_abort.py +29 -49
  108. adam/commands/reaper/reaper_schedule_activate.py +11 -30
  109. adam/commands/reaper/reaper_schedule_start.py +10 -29
  110. adam/commands/reaper/reaper_schedule_stop.py +10 -29
  111. adam/commands/reaper/reaper_schedules.py +4 -14
  112. adam/commands/reaper/reaper_status.py +8 -16
  113. adam/commands/reaper/utils_reaper.py +196 -0
  114. adam/commands/repair/repair.py +4 -22
  115. adam/commands/repair/repair_log.py +4 -7
  116. adam/commands/repair/repair_run.py +27 -29
  117. adam/commands/repair/repair_scan.py +31 -34
  118. adam/commands/repair/repair_stop.py +4 -7
  119. adam/commands/report.py +25 -21
  120. adam/commands/restart.py +25 -26
  121. adam/commands/rollout.py +19 -24
  122. adam/commands/shell.py +5 -4
  123. adam/commands/show/show.py +6 -19
  124. adam/commands/show/show_app_actions.py +26 -22
  125. adam/commands/show/show_app_id.py +8 -11
  126. adam/commands/show/show_app_queues.py +7 -10
  127. adam/commands/show/{show_repairs.py → show_cassandra_repairs.py} +8 -17
  128. adam/commands/show/show_cassandra_status.py +29 -33
  129. adam/commands/show/show_cassandra_version.py +4 -14
  130. adam/commands/show/show_commands.py +19 -21
  131. adam/commands/show/show_host.py +1 -1
  132. adam/commands/show/show_login.py +26 -24
  133. adam/commands/show/show_processes.py +16 -18
  134. adam/commands/show/show_storage.py +10 -20
  135. adam/commands/watch.py +26 -29
  136. adam/config.py +5 -14
  137. adam/embedded_params.py +1 -1
  138. adam/pod_exec_result.py +7 -1
  139. adam/repl.py +95 -131
  140. adam/repl_commands.py +48 -20
  141. adam/repl_state.py +270 -61
  142. adam/sql/sql_completer.py +105 -63
  143. adam/sql/sql_state_machine.py +618 -0
  144. adam/sql/term_completer.py +3 -0
  145. adam/sso/authn_ad.py +6 -5
  146. adam/sso/authn_okta.py +3 -3
  147. adam/sso/cred_cache.py +3 -2
  148. adam/sso/idp.py +3 -3
  149. adam/utils.py +439 -3
  150. adam/utils_app.py +98 -0
  151. adam/utils_athena.py +140 -87
  152. adam/utils_audits.py +106 -0
  153. adam/utils_issues.py +32 -0
  154. adam/utils_k8s/app_clusters.py +28 -0
  155. adam/utils_k8s/app_pods.py +33 -0
  156. adam/utils_k8s/cassandra_clusters.py +22 -20
  157. adam/utils_k8s/cassandra_nodes.py +4 -4
  158. adam/utils_k8s/custom_resources.py +5 -0
  159. adam/utils_k8s/ingresses.py +2 -2
  160. adam/utils_k8s/k8s.py +87 -0
  161. adam/utils_k8s/pods.py +77 -68
  162. adam/utils_k8s/secrets.py +4 -4
  163. adam/utils_k8s/service_accounts.py +5 -4
  164. adam/utils_k8s/services.py +2 -2
  165. adam/utils_k8s/statefulsets.py +1 -12
  166. adam/utils_net.py +4 -4
  167. adam/utils_repl/__init__.py +0 -0
  168. adam/utils_repl/automata_completer.py +48 -0
  169. adam/utils_repl/repl_completer.py +46 -0
  170. adam/utils_repl/state_machine.py +173 -0
  171. adam/utils_sqlite.py +109 -0
  172. adam/version.py +1 -1
  173. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/METADATA +1 -1
  174. kaqing-2.0.171.dist-info/RECORD +236 -0
  175. adam/commands/app.py +0 -67
  176. adam/commands/bash.py +0 -92
  177. adam/commands/cql/cql_table_completer.py +0 -8
  178. adam/commands/cql/cql_utils.py +0 -115
  179. adam/commands/describe/describe.py +0 -47
  180. adam/commands/describe/describe_keyspace.py +0 -60
  181. adam/commands/describe/describe_keyspaces.py +0 -49
  182. adam/commands/describe/describe_schema.py +0 -49
  183. adam/commands/describe/describe_table.py +0 -60
  184. adam/commands/describe/describe_tables.py +0 -49
  185. adam/commands/devices.py +0 -118
  186. adam/commands/postgres/postgres_utils.py +0 -31
  187. adam/commands/postgres/psql_table_completer.py +0 -11
  188. adam/commands/reaper/reaper_session.py +0 -159
  189. adam/sql/state_machine.py +0 -460
  190. kaqing-2.0.98.dist-info/RECORD +0 -191
  191. /adam/commands/{describe → devices}/__init__.py +0 -0
  192. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/WHEEL +0 -0
  193. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/entry_points.txt +0 -0
  194. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/top_level.txt +0 -0
adam/utils_k8s/k8s.py ADDED
@@ -0,0 +1,87 @@
1
+ from collections.abc import Callable
2
+ import re
3
+ import portforward
4
+
5
+ from adam.commands.command import InvalidState
6
+ from adam.repl_state import ReplState
7
+ from adam.utils import log2
8
+ from adam.utils_k8s.kube_context import KubeContext
9
+
10
+ class PortForwardHandler:
11
+ connections: dict[str, int] = {}
12
+
13
+ def __init__(self, state: ReplState, local_port: int, svc_or_pod: Callable[[bool],str], target_port: int):
14
+ self.state = state
15
+ self.local_port = local_port
16
+ self.svc_or_pod = svc_or_pod
17
+ self.target_port = target_port
18
+ self.forward_connection = None
19
+ self.pod = None
20
+
21
+ def __enter__(self) -> tuple[str, str]:
22
+ state = self.state
23
+
24
+ if not self.svc_or_pod:
25
+ log2('No service or pod found.')
26
+
27
+ raise InvalidState(state)
28
+
29
+ if KubeContext.in_cluster():
30
+ svc_name = self.svc_or_pod(True)
31
+ if not svc_name:
32
+ log2('No service found.')
33
+
34
+ raise InvalidState(state)
35
+
36
+ # cs-a526330d23-cs-a526330d23-default-sts-0 ->
37
+ # curl http://cs-a526330d23-cs-a526330d23-reaper-service.stgawsscpsr.svc.cluster.local:8080
38
+ groups = re.match(r'^(.*?-.*?-.*?-.*?-).*', state.sts)
39
+ if groups:
40
+ svc = f'{groups[1]}{svc_name}.{state.namespace}.svc.cluster.local:{self.target_port}'
41
+ return (svc, svc)
42
+ else:
43
+ raise InvalidState(state)
44
+ else:
45
+ pod = self.svc_or_pod(False)
46
+ if not pod:
47
+ log2('No pod found.')
48
+
49
+ raise InvalidState(state)
50
+
51
+ self.pod = pod
52
+ self.forward_connection = portforward.forward(state.namespace, pod, self.local_port, self.target_port)
53
+ if self.inc_connection_cnt() == 1:
54
+ self.forward_connection.__enter__()
55
+
56
+ return (f'localhost:{self.local_port}', f'{pod}:{self.target_port}')
57
+
58
+ def __exit__(self, exc_type, exc_val, exc_tb):
59
+ if self.forward_connection:
60
+ if not self.dec_connection_cnt():
61
+ return self.forward_connection.__exit__(exc_type, exc_val, exc_tb)
62
+
63
+ return False
64
+
65
+ def inc_connection_cnt(self):
66
+ id = self.connection_id(self.pod)
67
+ if id not in PortForwardHandler.connections:
68
+ PortForwardHandler.connections[id] = 1
69
+ else:
70
+ PortForwardHandler.connections[id] += 1
71
+
72
+ return PortForwardHandler.connections[id]
73
+
74
+ def dec_connection_cnt(self):
75
+ id = self.connection_id(self.pod)
76
+ if id not in PortForwardHandler.connections:
77
+ PortForwardHandler.connections[id] = 0
78
+ elif PortForwardHandler.connections[id] > 0:
79
+ PortForwardHandler.connections[id] -= 1
80
+
81
+ return PortForwardHandler.connections[id]
82
+
83
+ def connection_id(self, pod: str):
84
+ return f'{self.local_port}:{pod}:{self.target_port}'
85
+
86
+ def port_forwarding(state: ReplState, local_port: int, svc_or_pod: Callable[[bool],str], target_port: int):
87
+ return PortForwardHandler(state, local_port, svc_or_pod, target_port)
adam/utils_k8s/pods.py CHANGED
@@ -1,24 +1,27 @@
1
1
  from collections.abc import Callable
2
- from concurrent.futures import ThreadPoolExecutor, as_completed
3
2
  from datetime import datetime
4
3
  import sys
5
4
  import time
6
- from typing import TypeVar, cast
5
+ from typing import TypeVar
7
6
  from kubernetes import client
8
7
  from kubernetes.stream import stream
9
- from kubernetes.stream.ws_client import ERROR_CHANNEL
8
+ from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
10
9
 
11
10
  from adam.config import Config
12
11
  from adam.utils_k8s.volumes import ConfigMapMount
13
12
  from adam.pod_exec_result import PodExecResult
14
- from adam.utils import elapsed_time, log2, random_alphanumeric
13
+ from adam.utils import ParallelMapHandler, log2
15
14
  from .kube_context import KubeContext
16
15
 
16
+ from websocket._core import WebSocket
17
+
17
18
  T = TypeVar('T')
18
19
  _TEST_POD_EXEC_OUTS: PodExecResult = None
19
20
 
20
21
  # utility collection on pods; methods are all static
21
22
  class Pods:
23
+ _TEST_POD_CLOSE_SOCKET: bool = False
24
+
22
25
  def set_test_pod_exec_outs(outs: PodExecResult):
23
26
  global _TEST_POD_EXEC_OUTS
24
27
  _TEST_POD_EXEC_OUTS = outs
@@ -28,7 +31,7 @@ class Pods:
28
31
  def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
29
32
  try:
30
33
  v1 = client.CoreV1Api()
31
- api_response = v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
34
+ v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
32
35
  except Exception as e:
33
36
  log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
34
37
 
@@ -39,63 +42,20 @@ class Pods:
39
42
  for i in ret.items:
40
43
  v1.delete_namespaced_pod(name=i.metadata.name, namespace=namespace, grace_period_seconds=grace_period_seconds)
41
44
 
42
- def on_pods(pods: list[str],
43
- namespace: str,
44
- body: Callable[[ThreadPoolExecutor, str, str, bool], T],
45
- post: Callable[[T], T] = None,
46
- action: str = 'action', max_workers=0, show_out=True, on_any = False) -> list[T]:
47
- show_out = KubeContext.show_out(show_out)
48
-
45
+ def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, action: str = 'action'):
49
46
  if not max_workers:
50
47
  max_workers = Config().action_workers(action, 0)
51
- if not on_any and max_workers > 0:
52
- # if parallel, node sampling is suppressed
53
- if KubeContext.show_parallelism():
54
- log2(f'Executing on all nodes from statefulset in parallel...')
55
- start_time = time.time()
56
- try:
57
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
58
- # disable stdout from the pod_exec, then show the output in a for loop
59
- futures = [body(executor, pod, namespace, show_out) for pod in pods]
60
- if len(futures) == 0:
61
- return cast(list[T], [])
62
-
63
- rs = [future.result() for future in as_completed(futures)]
64
- if post:
65
- rs = [post(r, show_out=show_out) for r in rs]
66
-
67
- return rs
68
- finally:
69
- if KubeContext.show_parallelism():
70
- log2(f"Parallel {action} elapsed time: {elapsed_time(start_time)} with {max_workers} workers")
71
- else:
72
- results: list[T] = []
73
-
74
- samples = 1 if on_any else Config().action_node_samples(action, sys.maxsize)
75
- l = min(len(pods), samples)
76
- adj = 'all'
77
- if l < len(pods):
78
- adj = f'{l} sample'
79
- if show_out:
80
- log2(f'Executing on {adj} nodes from statefulset...')
81
- for pod_name in pods:
82
- try:
83
- # disable stdout from the pod_exec, then show the output in a for loop
84
- result = body(None, pod_name, namespace, False)
85
- if post:
86
- result = post(result, show_out=show_out)
87
- results.append(result)
88
- if result:
89
- l -= 1
90
- if not l:
91
- break
92
- except Exception as e:
93
- log2(e)
94
-
95
- return results
96
-
97
- def exec(pod_name: str, container: str, namespace: str, command: str, show_out = True, throw_err = False, shell = '/bin/sh',
98
- interaction: Callable[[any, list[str]], any] = None):
48
+ if samples == sys.maxsize:
49
+ samples = Config().action_node_samples(action, sys.maxsize)
50
+
51
+ return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
52
+
53
+ def exec(pod_name: str, container: str, namespace: str, command: str,
54
+ show_out = True, throw_err = False, shell = '/bin/sh',
55
+ background = False,
56
+ log_file = None,
57
+ interaction: Callable[[any, list[str]], any] = None,
58
+ env_prefix: str = None):
99
59
  if _TEST_POD_EXEC_OUTS:
100
60
  return _TEST_POD_EXEC_OUTS
101
61
 
@@ -103,11 +63,13 @@ class Pods:
103
63
 
104
64
  api = client.CoreV1Api()
105
65
 
106
- log_file = None
107
66
  tty = True
108
67
  exec_command = [shell, '-c', command]
109
- if command.endswith(' &'):
110
- # should be false for starting a backgroud process
68
+ if env_prefix:
69
+ exec_command = [shell, '-c', f'{env_prefix} {command}']
70
+
71
+ if background or command.endswith(' &'):
72
+ # should be false for starting a background process
111
73
  tty = False
112
74
 
113
75
  if Config().get('repl.background-process.auto-nohup', True):
@@ -116,15 +78,18 @@ class Pods:
116
78
  if command.startswith('nodetool '):
117
79
  cmd_name = f".{'_'.join(command.split(' ')[5:])}"
118
80
 
119
- log_file = f'/tmp/qing-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
81
+ if not log_file:
82
+ log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
120
83
  command = f"nohup {command} > {log_file} 2>&1 &"
84
+ if env_prefix:
85
+ command = f'{env_prefix} {command}'
121
86
  exec_command = [shell, '-c', command]
122
87
 
123
88
  k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
124
- if show_out:
125
- print(k_command)
89
+ if Config().is_debug():
90
+ log2(k_command)
126
91
 
127
- resp = stream(
92
+ resp: WSClient = stream(
128
93
  api.connect_get_namespaced_pod_exec,
129
94
  pod_name,
130
95
  namespace,
@@ -137,6 +102,7 @@ class Pods:
137
102
  _preload_content=False,
138
103
  )
139
104
 
105
+ s: WebSocket = resp.sock
140
106
  stdout = []
141
107
  stderr = []
142
108
  error_output = None
@@ -167,9 +133,49 @@ class Pods:
167
133
  log2(e)
168
134
  finally:
169
135
  resp.close()
136
+ if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
137
+ try:
138
+ s.sock.close()
139
+ except:
140
+ pass
170
141
 
171
142
  return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
172
143
 
144
+ def read_file(pod_name: str, container: str, namespace: str, file_path: str):
145
+ v1 = client.CoreV1Api()
146
+
147
+ resp = stream(
148
+ v1.connect_get_namespaced_pod_exec,
149
+ name=pod_name,
150
+ namespace=namespace,
151
+ container=container,
152
+ command=["cat", file_path],
153
+ stderr=True, stdin=False,
154
+ stdout=True, tty=False,
155
+ _preload_content=False, # Important for streaming
156
+ )
157
+
158
+ s: WebSocket = resp.sock
159
+ try:
160
+ while resp.is_open():
161
+ resp.update(timeout=1)
162
+ if resp.peek_stdout():
163
+ yield resp.read_stdout()
164
+
165
+ try:
166
+ # get the exit code from server
167
+ error_output = resp.read_channel(ERROR_CHANNEL)
168
+ except Exception as e:
169
+ pass
170
+ except Exception as e:
171
+ raise e
172
+ finally:
173
+ resp.close()
174
+ if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
175
+ try:
176
+ s.sock.close()
177
+ except:
178
+ pass
173
179
  def get_container(namespace: str, pod_name: str, container_name: str):
174
180
  pod = Pods.get(namespace, pod_name)
175
181
  if not pod:
@@ -278,4 +284,7 @@ class Pods:
278
284
  log2(' Timed Out')
279
285
 
280
286
  def completed(namespace: str, pod_name: str):
281
- return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
287
+ return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
288
+
289
+ def log_prefix():
290
+ return Config().get('log-prefix', '/tmp/qing')
adam/utils_k8s/secrets.py CHANGED
@@ -6,13 +6,13 @@ from kubernetes import client
6
6
  from kubernetes.client import V1Secret
7
7
 
8
8
  from adam.config import Config
9
- from adam.utils import log2
9
+ from adam.utils import log2, wait_log
10
10
 
11
11
  # utility collection on secrets; methods are all static
12
12
  class Secrets:
13
13
  @functools.lru_cache()
14
14
  def list_secrets(namespace: str = None, name_pattern: str = None):
15
- Config().wait_log('Inspecting Cassandra Instances...')
15
+ wait_log('Inspecting Cassandra Instances...')
16
16
 
17
17
  secrets_names = []
18
18
 
@@ -39,14 +39,14 @@ class Secrets:
39
39
 
40
40
  return secrets_names
41
41
 
42
- def get_user_pass(ss_name: str, namespace: str, secret_path: str = 'cql.secret'):
42
+ def get_user_pass(sts_or_pod_name: str, namespace: str, secret_path: str = 'cql.secret'):
43
43
  # cs-d0767a536f-cs-d0767a536f-default-sts ->
44
44
  # cs-d0767a536f-superuser
45
45
  # cs-d0767a536f-reaper-ui
46
46
  user = 'superuser'
47
47
  if secret_path == 'reaper.secret':
48
48
  user = 'reaper-ui'
49
- groups = re.match(Config().get(f'{secret_path}.cluster-regex', r'(.*?-.*?)-.*'), ss_name)
49
+ groups = re.match(Config().get(f'{secret_path}.cluster-regex', r'(.*?-.*?)-.*'), sts_or_pod_name)
50
50
  secret_name = Config().get(f'{secret_path}.name', '{cluster}-' + user).replace('{cluster}', groups[1], 1)
51
51
 
52
52
  secret = Secrets.get_data(namespace, secret_name)
@@ -1,6 +1,7 @@
1
1
  from kubernetes import client, config
2
2
 
3
3
  from adam.config import Config
4
+ from adam.utils import debug
4
5
 
5
6
  # utility collection on service accounts; methods are all static
6
7
  class ServiceAccounts:
@@ -37,7 +38,7 @@ class ServiceAccounts:
37
38
  namespace=namespace,
38
39
  body=service_account
39
40
  )
40
- Config().debug(f"Service Account '{api_response.metadata.name}' created in namespace '{namespace}'.")
41
+ debug(f"Service Account '{api_response.metadata.name}' created in namespace '{namespace}'.")
41
42
 
42
43
  def delete_service_account(namespace: str, label_selector: str) -> list:
43
44
  refs = []
@@ -45,7 +46,7 @@ class ServiceAccounts:
45
46
  v1 = client.CoreV1Api()
46
47
  sas = v1.list_namespaced_service_account(namespace=namespace, label_selector=label_selector).items
47
48
  for sa in sas:
48
- Config().debug(f'delete {sa.metadata.name}')
49
+ debug(f'delete {sa.metadata.name}')
49
50
  v1.delete_namespaced_service_account(name=sa.metadata.name, namespace=namespace)
50
51
  refs.append(sa)
51
52
 
@@ -102,7 +103,7 @@ class ServiceAccounts:
102
103
  v1_rbac = client.RbacAuthorizationV1Api()
103
104
  cluster_role_bindings = v1_rbac.list_namespaced_role_binding(namespace=namespace, label_selector=label_selector).items
104
105
  for binding in cluster_role_bindings:
105
- Config().debug(f'delete {binding.metadata.name}')
106
+ debug(f'delete {binding.metadata.name}')
106
107
  v1_rbac.delete_namespaced_role_binding(name=binding.metadata.name, namespace=namespace)
107
108
  refs.append(binding)
108
109
 
@@ -162,7 +163,7 @@ class ServiceAccounts:
162
163
  v1_rbac = client.RbacAuthorizationV1Api()
163
164
  cluster_role_bindings = v1_rbac.list_cluster_role_binding(label_selector=label_selector).items
164
165
  for binding in cluster_role_bindings:
165
- Config().debug(f'delete {binding.metadata.name}')
166
+ debug(f'delete {binding.metadata.name}')
166
167
  v1_rbac.delete_cluster_role_binding(binding.metadata.name)
167
168
  refs.append(binding)
168
169
 
@@ -2,7 +2,7 @@ from typing import List
2
2
  from kubernetes import client
3
3
 
4
4
  from adam.config import Config
5
- from adam.utils import log2
5
+ from adam.utils import debug, log2
6
6
 
7
7
  from .kube_context import KubeContext
8
8
 
@@ -71,7 +71,7 @@ class Services:
71
71
  namespace=namespace,
72
72
  body=delete_options
73
73
  )
74
- Config().debug(f"200 Service '{name}' in namespace '{namespace}' deleted successfully.")
74
+ debug(f"200 Service '{name}' in namespace '{namespace}' deleted successfully.")
75
75
  except client.ApiException as e:
76
76
  log2(f"Error deleting Service '{name}': {e}")
77
77
  except Exception as e:
@@ -1,12 +1,9 @@
1
- from collections.abc import Callable
2
- from concurrent.futures import ThreadPoolExecutor
3
1
  from datetime import datetime
4
2
  import functools
5
3
  import re
6
4
  from typing import List, TypeVar, cast
7
5
  from kubernetes import client
8
6
 
9
- from .pods import Pods
10
7
  from .kube_context import KubeContext
11
8
  from adam.utils import log2
12
9
 
@@ -58,15 +55,6 @@ class StatefulSets:
58
55
 
59
56
  return statefulset_pods
60
57
 
61
- def on_cluster(statefulset: str,
62
- namespace: str,
63
- body: Callable[[ThreadPoolExecutor, str, str, bool], T],
64
- post: Callable[[T], T] = None,
65
- action: str = 'action', max_workers=0, show_out=True, on_any = False) -> list[T]:
66
- pods = StatefulSets.pod_names(statefulset, namespace)
67
-
68
- return Pods.on_pods(pods, namespace, body, post=post, action=action, max_workers=max_workers, show_out=show_out, on_any=on_any)
69
-
70
58
  @functools.lru_cache()
71
59
  def pod_names(ss: str, ns: str):
72
60
  return [pod.metadata.name for pod in StatefulSets.pods(ss, ns)]
@@ -92,6 +80,7 @@ class StatefulSets:
92
80
 
93
81
  return restarted, False
94
82
 
83
+ @functools.lru_cache()
95
84
  def get_datacenter(sts: str, ns: str) -> str:
96
85
  v1 = client.AppsV1Api()
97
86
  namespace = ns
adam/utils_net.py CHANGED
@@ -18,7 +18,7 @@ def get_my_host():
18
18
  return MY_HOST
19
19
 
20
20
  def get_ip_from_hostname(hostname):
21
- try:
22
- return socket.gethostbyname(hostname)
23
- except socket.gaierror:
24
- return None
21
+ try:
22
+ return socket.gethostbyname(hostname)
23
+ except socket.gaierror:
24
+ return None
File without changes
@@ -0,0 +1,48 @@
1
+ from typing import Generic, Iterable, TypeVar
2
+ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, WordCompleter
3
+ from prompt_toolkit.document import Document
4
+
5
+ from adam.utils_repl.state_machine import StateMachine, State
6
+
7
+ __all__ = [
8
+ "AutomataCompleter",
9
+ ]
10
+
11
+ T = TypeVar('T')
12
+
13
+ class AutomataCompleter(Completer, Generic[T]):
14
+ def __init__(self,
15
+ state_machine: StateMachine,
16
+ first_term: str = '',
17
+ debug = False):
18
+ super().__init__()
19
+ self.machine = state_machine
20
+ self.first_term = first_term
21
+ self.debug = debug
22
+
23
+ def get_completions(
24
+ self, document: Document, complete_event: CompleteEvent
25
+ ) -> Iterable[Completion]:
26
+ text = document.text_before_cursor.lstrip()
27
+ state = ''
28
+ if self.first_term:
29
+ text = f'{self.first_term} {text}'
30
+
31
+ completer: Completer = None
32
+ state: State = self.machine.traverse_tokens(self.tokens(text), State(state))
33
+ if self.debug:
34
+ print('\n =>', state.state if isinstance(state, State) else '')
35
+
36
+ if state.state in self.machine.suggestions:
37
+ if completer := self.suggestions_completer(state, self.machine.suggestions[state.state].strip(' ')):
38
+ for c in completer.get_completions(document, complete_event):
39
+ yield c
40
+
41
+ def tokens(self, text: str) -> list[T]:
42
+ return text.split(' ')
43
+
44
+ def suggestions_completer(self, _: State, suggestions: str) -> list[str]:
45
+ if not suggestions:
46
+ return None
47
+
48
+ return WordCompleter(suggestions.split(','))
@@ -0,0 +1,46 @@
1
+ import re
2
+ from typing import Iterable, TypeVar
3
+ from prompt_toolkit.completion import CompleteEvent, Completion, NestedCompleter, WordCompleter
4
+ from prompt_toolkit.document import Document
5
+
6
+ __all__ = [
7
+ "ReplCompleter",
8
+ ]
9
+
10
+ T = TypeVar('T')
11
+
12
+ class ReplCompleter(NestedCompleter):
13
+ def get_completions(
14
+ self, document: Document, complete_event: CompleteEvent
15
+ ) -> Iterable[Completion]:
16
+ # Split document.
17
+ text = document.text_before_cursor.lstrip()
18
+ stripped_len = len(document.text_before_cursor) - len(text)
19
+
20
+ # If there is a space, check for the first term, and use a
21
+ # subcompleter.
22
+ if " " in text:
23
+ first_term = text.split()[0]
24
+ completer = self.options.get(first_term)
25
+
26
+ # If we have a sub completer, use this for the completions.
27
+ if completer is not None:
28
+ remaining_text = text[len(first_term) :].lstrip()
29
+ move_cursor = len(text) - len(remaining_text) + stripped_len
30
+
31
+ new_document = Document(
32
+ remaining_text,
33
+ cursor_position=document.cursor_position - move_cursor,
34
+ )
35
+
36
+ for c in completer.get_completions(new_document, complete_event):
37
+ yield c
38
+
39
+ # No space in the input: behave exactly like `WordCompleter`.
40
+ else:
41
+ completer = WordCompleter(
42
+ # Allow dot in the middle or a word
43
+ list(self.options.keys()), ignore_case=self.ignore_case, pattern=re.compile(r"([a-zA-Z0-9_\.\@\&]+|[^a-zA-Z0-9_\.\@\&\s]+)")
44
+ )
45
+ for c in completer.get_completions(document, complete_event):
46
+ yield c