kaqing 2.0.115__py3-none-any.whl → 2.0.172__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 (187) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +8 -11
  3. adam/batch.py +3 -3
  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/checks/disk.py +2 -3
  8. adam/columns/columns.py +3 -1
  9. adam/columns/cpu.py +3 -1
  10. adam/columns/cpu_metrics.py +22 -0
  11. adam/columns/memory.py +3 -4
  12. adam/commands/__init__.py +18 -0
  13. adam/commands/alter_tables.py +43 -47
  14. adam/commands/audit/audit.py +24 -25
  15. adam/commands/audit/audit_repair_tables.py +14 -17
  16. adam/commands/audit/audit_run.py +15 -23
  17. adam/commands/audit/show_last10.py +10 -13
  18. adam/commands/audit/show_slow10.py +10 -13
  19. adam/commands/audit/show_top10.py +10 -14
  20. adam/commands/audit/utils_show_top10.py +2 -3
  21. adam/commands/bash/__init__.py +5 -0
  22. adam/commands/bash/bash.py +8 -96
  23. adam/commands/bash/utils_bash.py +16 -0
  24. adam/commands/cat.py +14 -19
  25. adam/commands/cd.py +12 -100
  26. adam/commands/check.py +20 -21
  27. adam/commands/cli_commands.py +2 -3
  28. adam/commands/code.py +20 -23
  29. adam/commands/command.py +123 -39
  30. adam/commands/commands_utils.py +8 -17
  31. adam/commands/cp.py +33 -39
  32. adam/commands/cql/cql_completions.py +28 -10
  33. adam/commands/cql/cqlsh.py +10 -30
  34. adam/commands/cql/utils_cql.py +343 -0
  35. adam/commands/deploy/code_start.py +7 -10
  36. adam/commands/deploy/code_stop.py +4 -21
  37. adam/commands/deploy/code_utils.py +3 -3
  38. adam/commands/deploy/deploy.py +4 -27
  39. adam/commands/deploy/deploy_frontend.py +14 -17
  40. adam/commands/deploy/deploy_pg_agent.py +2 -5
  41. adam/commands/deploy/deploy_pod.py +65 -73
  42. adam/commands/deploy/deploy_utils.py +14 -24
  43. adam/commands/deploy/undeploy.py +4 -27
  44. adam/commands/deploy/undeploy_frontend.py +4 -7
  45. adam/commands/deploy/undeploy_pg_agent.py +5 -7
  46. adam/commands/deploy/undeploy_pod.py +11 -12
  47. adam/commands/devices/__init__.py +0 -0
  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/clean_up_all_export_sessions.py +37 -0
  57. adam/commands/export/clean_up_export_sessions.py +51 -0
  58. adam/commands/export/drop_export_database.py +55 -0
  59. adam/commands/export/drop_export_databases.py +43 -0
  60. adam/commands/export/export.py +19 -26
  61. adam/commands/export/export_databases.py +174 -0
  62. adam/commands/export/export_handlers.py +71 -0
  63. adam/commands/export/export_select.py +48 -22
  64. adam/commands/export/export_select_x.py +54 -0
  65. adam/commands/export/export_use.py +19 -23
  66. adam/commands/export/exporter.py +353 -0
  67. adam/commands/export/import_session.py +40 -0
  68. adam/commands/export/importer.py +67 -0
  69. adam/commands/export/importer_athena.py +77 -0
  70. adam/commands/export/importer_sqlite.py +39 -0
  71. adam/commands/export/show_column_counts.py +54 -0
  72. adam/commands/export/show_export_databases.py +36 -0
  73. adam/commands/export/show_export_session.py +48 -0
  74. adam/commands/export/show_export_sessions.py +44 -0
  75. adam/commands/export/utils_export.py +223 -162
  76. adam/commands/help.py +1 -1
  77. adam/commands/intermediate_command.py +49 -0
  78. adam/commands/issues.py +11 -43
  79. adam/commands/kubectl.py +3 -6
  80. adam/commands/login.py +22 -24
  81. adam/commands/logs.py +3 -6
  82. adam/commands/ls.py +11 -128
  83. adam/commands/medusa/medusa.py +4 -22
  84. adam/commands/medusa/medusa_backup.py +20 -24
  85. adam/commands/medusa/medusa_restore.py +29 -33
  86. adam/commands/medusa/medusa_show_backupjobs.py +14 -18
  87. adam/commands/medusa/medusa_show_restorejobs.py +11 -18
  88. adam/commands/nodetool.py +6 -15
  89. adam/commands/param_get.py +11 -12
  90. adam/commands/param_set.py +9 -10
  91. adam/commands/postgres/postgres.py +41 -34
  92. adam/commands/postgres/postgres_context.py +57 -24
  93. adam/commands/postgres/postgres_ls.py +4 -8
  94. adam/commands/postgres/postgres_preview.py +5 -9
  95. adam/commands/postgres/psql_completions.py +1 -1
  96. adam/commands/postgres/utils_postgres.py +66 -0
  97. adam/commands/preview_table.py +5 -44
  98. adam/commands/pwd.py +14 -47
  99. adam/commands/reaper/reaper.py +4 -27
  100. adam/commands/reaper/reaper_forward.py +48 -55
  101. adam/commands/reaper/reaper_forward_session.py +6 -0
  102. adam/commands/reaper/reaper_forward_stop.py +10 -16
  103. adam/commands/reaper/reaper_restart.py +7 -14
  104. adam/commands/reaper/reaper_run_abort.py +11 -30
  105. adam/commands/reaper/reaper_runs.py +42 -57
  106. adam/commands/reaper/reaper_runs_abort.py +29 -49
  107. adam/commands/reaper/reaper_schedule_activate.py +11 -30
  108. adam/commands/reaper/reaper_schedule_start.py +10 -29
  109. adam/commands/reaper/reaper_schedule_stop.py +10 -29
  110. adam/commands/reaper/reaper_schedules.py +4 -14
  111. adam/commands/reaper/reaper_status.py +8 -16
  112. adam/commands/reaper/utils_reaper.py +196 -0
  113. adam/commands/repair/repair.py +4 -22
  114. adam/commands/repair/repair_log.py +5 -11
  115. adam/commands/repair/repair_run.py +27 -34
  116. adam/commands/repair/repair_scan.py +32 -38
  117. adam/commands/repair/repair_stop.py +5 -11
  118. adam/commands/report.py +27 -29
  119. adam/commands/restart.py +25 -26
  120. adam/commands/rollout.py +19 -24
  121. adam/commands/shell.py +10 -4
  122. adam/commands/show/show.py +10 -25
  123. adam/commands/show/show_cassandra_repairs.py +35 -0
  124. adam/commands/show/show_cassandra_status.py +32 -43
  125. adam/commands/show/show_cassandra_version.py +5 -18
  126. adam/commands/show/show_commands.py +19 -24
  127. adam/commands/show/show_host.py +1 -1
  128. adam/commands/show/show_login.py +20 -27
  129. adam/commands/show/show_processes.py +15 -19
  130. adam/commands/show/show_storage.py +10 -20
  131. adam/commands/watch.py +26 -29
  132. adam/config.py +5 -14
  133. adam/embedded_params.py +1 -1
  134. adam/log.py +4 -4
  135. adam/pod_exec_result.py +3 -3
  136. adam/repl.py +40 -103
  137. adam/repl_commands.py +32 -16
  138. adam/repl_state.py +57 -28
  139. adam/sql/sql_completer.py +44 -28
  140. adam/sql/sql_state_machine.py +89 -28
  141. adam/sso/authn_ad.py +6 -8
  142. adam/sso/authn_okta.py +4 -6
  143. adam/sso/cred_cache.py +3 -5
  144. adam/sso/idp.py +9 -12
  145. adam/utils.py +435 -6
  146. adam/utils_athena.py +57 -37
  147. adam/utils_audits.py +12 -14
  148. adam/utils_issues.py +32 -0
  149. adam/utils_k8s/app_clusters.py +13 -18
  150. adam/utils_k8s/app_pods.py +2 -0
  151. adam/utils_k8s/cassandra_clusters.py +22 -19
  152. adam/utils_k8s/cassandra_nodes.py +2 -2
  153. adam/utils_k8s/custom_resources.py +16 -17
  154. adam/utils_k8s/ingresses.py +2 -2
  155. adam/utils_k8s/jobs.py +7 -11
  156. adam/utils_k8s/k8s.py +87 -0
  157. adam/utils_k8s/pods.py +40 -77
  158. adam/utils_k8s/secrets.py +4 -4
  159. adam/utils_k8s/service_accounts.py +5 -4
  160. adam/utils_k8s/services.py +2 -2
  161. adam/utils_k8s/statefulsets.py +1 -12
  162. adam/utils_net.py +4 -4
  163. adam/utils_repl/__init__.py +0 -0
  164. adam/utils_repl/automata_completer.py +48 -0
  165. adam/utils_repl/repl_completer.py +46 -0
  166. adam/utils_repl/state_machine.py +173 -0
  167. adam/utils_sqlite.py +137 -0
  168. adam/version.py +1 -1
  169. {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/METADATA +1 -1
  170. kaqing-2.0.172.dist-info/RECORD +230 -0
  171. adam/commands/app.py +0 -67
  172. adam/commands/app_ping.py +0 -44
  173. adam/commands/cql/cql_utils.py +0 -204
  174. adam/commands/devices.py +0 -147
  175. adam/commands/export/export_on_x.py +0 -76
  176. adam/commands/export/export_rmdbs.py +0 -65
  177. adam/commands/postgres/postgres_utils.py +0 -31
  178. adam/commands/reaper/reaper_session.py +0 -159
  179. adam/commands/show/show_app_actions.py +0 -56
  180. adam/commands/show/show_app_id.py +0 -47
  181. adam/commands/show/show_app_queues.py +0 -45
  182. adam/commands/show/show_repairs.py +0 -47
  183. adam/utils_export.py +0 -42
  184. kaqing-2.0.115.dist-info/RECORD +0 -203
  185. {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/WHEEL +0 -0
  186. {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/entry_points.txt +0 -0
  187. {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/top_level.txt +0 -0
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
13
+ from adam.utils import ParallelMapHandler, log2, debug, log_exc
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
@@ -26,11 +29,9 @@ class Pods:
26
29
  return _TEST_POD_EXEC_OUTS
27
30
 
28
31
  def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
29
- try:
32
+ with log_exc(lambda e: "Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e):
30
33
  v1 = client.CoreV1Api()
31
34
  v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
32
- except Exception as e:
33
- log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
34
35
 
35
36
  def delete_with_selector(namespace: str, label_selector: str, grace_period_seconds: int = None):
36
37
  v1 = client.CoreV1Api()
@@ -39,69 +40,20 @@ class Pods:
39
40
  for i in ret.items:
40
41
  v1.delete_namespaced_pod(name=i.metadata.name, namespace=namespace, grace_period_seconds=grace_period_seconds)
41
42
 
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',
47
- max_workers=0,
48
- show_out=True,
49
- on_any = False,
50
- background = False) -> list[T]:
51
- show_out = KubeContext.show_out(show_out)
52
-
43
+ def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, action: str = 'action'):
53
44
  if not max_workers:
54
45
  max_workers = Config().action_workers(action, 0)
55
- if not on_any and max_workers > 0:
56
- # if parallel, node sampling is suppressed
57
- if KubeContext.show_parallelism():
58
- log2(f'Executing on all nodes from statefulset in parallel...')
59
- start_time = time.time()
60
- try:
61
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
62
- # disable stdout from the pod_exec, then show the output in a for loop
63
- futures = [body(executor, pod, namespace, show_out) for pod in pods]
64
- if len(futures) == 0:
65
- return cast(list[T], [])
66
-
67
- rs = [future.result() for future in as_completed(futures)]
68
- if post:
69
- rs = [post(r, show_out=show_out) for r in rs]
70
-
71
- return rs
72
- finally:
73
- if KubeContext.show_parallelism():
74
- log2(f"Parallel {action} elapsed time: {elapsed_time(start_time)} with {max_workers} workers")
75
- else:
76
- results: list[T] = []
77
-
78
- samples = 1 if on_any else Config().action_node_samples(action, sys.maxsize)
79
- l = min(len(pods), samples)
80
- adj = 'all'
81
- if l < len(pods):
82
- adj = f'{l} sample'
83
- if show_out:
84
- log2(f'Executing on {adj} nodes from statefulset...')
85
- for pod_name in pods:
86
- try:
87
- # disable stdout from the pod_exec, then show the output in a for loop
88
- result = body(None, pod_name, namespace, False)
89
- if post:
90
- result = post(result, show_out=show_out)
91
- results.append(result)
92
- if result:
93
- l -= 1
94
- if not l:
95
- break
96
- except Exception as e:
97
- log2(e)
98
-
99
- return results
46
+ if samples == sys.maxsize:
47
+ samples = Config().action_node_samples(action, sys.maxsize)
48
+
49
+ return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
100
50
 
101
51
  def exec(pod_name: str, container: str, namespace: str, command: str,
102
52
  show_out = True, throw_err = False, shell = '/bin/sh',
103
53
  background = False,
104
- interaction: Callable[[any, list[str]], any] = None):
54
+ log_file = None,
55
+ interaction: Callable[[any, list[str]], any] = None,
56
+ env_prefix: str = None):
105
57
  if _TEST_POD_EXEC_OUTS:
106
58
  return _TEST_POD_EXEC_OUTS
107
59
 
@@ -109,9 +61,11 @@ class Pods:
109
61
 
110
62
  api = client.CoreV1Api()
111
63
 
112
- log_file = None
113
64
  tty = True
114
65
  exec_command = [shell, '-c', command]
66
+ if env_prefix:
67
+ exec_command = [shell, '-c', f'{env_prefix} {command}']
68
+
115
69
  if background or command.endswith(' &'):
116
70
  # should be false for starting a background process
117
71
  tty = False
@@ -122,15 +76,17 @@ class Pods:
122
76
  if command.startswith('nodetool '):
123
77
  cmd_name = f".{'_'.join(command.split(' ')[5:])}"
124
78
 
125
- log_file = f'/tmp/qing-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
79
+ if not log_file:
80
+ log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
126
81
  command = f"nohup {command} > {log_file} 2>&1 &"
82
+ if env_prefix:
83
+ command = f'{env_prefix} {command}'
127
84
  exec_command = [shell, '-c', command]
128
85
 
129
86
  k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
130
- if show_out:
131
- print(k_command)
87
+ debug(k_command)
132
88
 
133
- resp = stream(
89
+ resp: WSClient = stream(
134
90
  api.connect_get_namespaced_pod_exec,
135
91
  pod_name,
136
92
  namespace,
@@ -143,6 +99,7 @@ class Pods:
143
99
  _preload_content=False,
144
100
  )
145
101
 
102
+ s: WebSocket = resp.sock
146
103
  stdout = []
147
104
  stderr = []
148
105
  error_output = None
@@ -161,11 +118,9 @@ class Pods:
161
118
  stderr.append(frag)
162
119
  if show_out: print(frag, end="")
163
120
 
164
- try:
121
+ with log_exc():
165
122
  # get the exit code from server
166
123
  error_output = resp.read_channel(ERROR_CHANNEL)
167
- except Exception as e:
168
- pass
169
124
  except Exception as e:
170
125
  if throw_err:
171
126
  raise e
@@ -173,6 +128,9 @@ class Pods:
173
128
  log2(e)
174
129
  finally:
175
130
  resp.close()
131
+ if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
132
+ with log_exc():
133
+ s.sock.close()
176
134
 
177
135
  return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
178
136
 
@@ -187,24 +145,26 @@ class Pods:
187
145
  command=["cat", file_path],
188
146
  stderr=True, stdin=False,
189
147
  stdout=True, tty=False,
190
- _preload_content=False # Important for streaming
148
+ _preload_content=False, # Important for streaming
191
149
  )
192
150
 
151
+ s: WebSocket = resp.sock
193
152
  try:
194
153
  while resp.is_open():
195
154
  resp.update(timeout=1)
196
155
  if resp.peek_stdout():
197
156
  yield resp.read_stdout()
198
157
 
199
- try:
158
+ with log_exc():
200
159
  # get the exit code from server
201
160
  error_output = resp.read_channel(ERROR_CHANNEL)
202
- except Exception as e:
203
- pass
204
161
  except Exception as e:
205
162
  raise e
206
163
  finally:
207
164
  resp.close()
165
+ if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
166
+ with log_exc():
167
+ s.sock.close()
208
168
 
209
169
  def get_container(namespace: str, pod_name: str, container_name: str):
210
170
  pod = Pods.get(namespace, pod_name)
@@ -314,4 +274,7 @@ class Pods:
314
274
  log2(' Timed Out')
315
275
 
316
276
  def completed(namespace: str, pod_name: str):
317
- return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
277
+ return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
278
+
279
+ def log_prefix():
280
+ 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, background = 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, background=background)
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
@@ -0,0 +1,173 @@
1
+ from abc import abstractmethod
2
+ from typing import Generic, TypeVar
3
+
4
+ from adam.utils import log_exc
5
+
6
+ __all__ = [
7
+ 'State',
8
+ 'StateMachine',
9
+ ]
10
+
11
+ T = TypeVar('T')
12
+
13
+ class State:
14
+ def __init__(self, state: str, comeback_token: str = None, comeback_state: str = None):
15
+ self.state = state
16
+ self.comeback_token = comeback_token
17
+ self.comeback_state = comeback_state
18
+ self.context: dict[str, str] = {}
19
+
20
+ def __str__(self):
21
+ return f'{self.state if self.state else None} comeback[{self.comeback_token} {self.comeback_state}]'
22
+
23
+ class StateMachine(Generic[T]):
24
+ @abstractmethod
25
+ def spec(self) -> str:
26
+ return None
27
+
28
+ @abstractmethod
29
+ def keywords(self) -> list[str]:
30
+ return None
31
+
32
+ def expandable_names(self):
33
+ return []
34
+
35
+ def incomplete_name_transition_condition(self, from_s: str, token: str, to_s: str, suggestions: str) -> list[str]:
36
+ if not suggestions:
37
+ return None
38
+
39
+ tokens = [token]
40
+ if '|' in token:
41
+ tokens = token.split('|')
42
+
43
+ if 'name' not in tokens:
44
+ return None
45
+
46
+ return tokens
47
+
48
+ def __init__(self, indent=0, push_level = 0, debug = False):
49
+ self.states: dict[str, State] = {}
50
+ self.suggestions: dict[str, str] = {}
51
+
52
+ self.indent = indent
53
+ self.push_level = push_level
54
+ self.comebacks: dict[int, str] = {}
55
+ self.debug = debug
56
+
57
+ from_ss_to_add = []
58
+ from_ss = ['']
59
+ words: str = None
60
+ for l in self.spec():
61
+ t_and_w = l.split('^')
62
+ if len(t_and_w) > 1:
63
+ words = t_and_w[1].strip()
64
+ else:
65
+ words = None
66
+
67
+ tks = t_and_w[0].strip(' ').split('>')
68
+ if not l.startswith('-'):
69
+ if words:
70
+ self.suggestions[tks[0].strip(' ')] = words
71
+
72
+ if len(tks) == 1:
73
+ from_ss_to_add.append(tks[0].strip(' '))
74
+ continue
75
+
76
+ from_ss = []
77
+ from_ss.extend(from_ss_to_add)
78
+ from_ss_to_add = []
79
+ from_ss.append(tks[0].strip(' '))
80
+
81
+ self.add_transitions(from_ss, tks, words)
82
+
83
+ def add_transitions(self, from_ss: list[str], tks: list[str], words: str):
84
+ token = tks[1].strip(' ')
85
+ if len(tks) > 2:
86
+ to_s = tks[2].strip(' ')
87
+ for from_s in from_ss:
88
+ self.add_whitespace_transition(from_s, to_s)
89
+ self.add_transition(from_s, token, to_s)
90
+ self.add_incomplete_name_transition(from_s, token, to_s, words)
91
+ elif '<' in tks[0]:
92
+ from_and_token = tks[0].split('<')
93
+ if len(from_and_token) > 1:
94
+ for from_s in from_ss:
95
+ self.add_comeback_transition(from_s, from_and_token[1], tks[1].strip(' '))
96
+
97
+ def add_whitespace_transition(self, from_s: str, to_s: str):
98
+ if self.witespace_transition_condition(from_s, to_s):
99
+ if self.debug:
100
+ print(f'{from_s[:-1]} > _ = {to_s}')
101
+ self.states[f'{from_s[:-1]} > _'] = State(from_s)
102
+
103
+ def witespace_transition_condition(self, from_s: str, to_s: str):
104
+ return from_s.endswith('_')
105
+
106
+ def add_incomplete_name_transition(self, from_s: str, token: str, to_s: str, words: str):
107
+ if tokens := self.incomplete_name_transition_condition(from_s, token, to_s, words):
108
+ self.suggestions[to_s] = words
109
+ for token in tokens:
110
+ if self.debug:
111
+ print(f'{to_s} > {token} = {to_s}')
112
+ self.states[f'{to_s} > {token}'] = State(to_s)
113
+
114
+ def add_transition(self, from_s: str, token: str, to_s: str):
115
+ tokens = [token]
116
+ if '|' in token:
117
+ tokens = token.split('|')
118
+
119
+ for t in tokens:
120
+ if t == '_or_':
121
+ t = '||'
122
+ elif t == 'pipe':
123
+ t = '|'
124
+ elif t == '_rdr0_':
125
+ t = '<'
126
+ elif t == '_rdr1_':
127
+ t = '>'
128
+ elif t == '_rdr2_':
129
+ t = '2>'
130
+
131
+ if self.debug:
132
+ print(f'{from_s} > {t} = {to_s}')
133
+ self.states[f'{from_s} > {t}'] = State(to_s)
134
+
135
+ def add_comeback_transition(self, from_s: str, token: str, to_s: str):
136
+ key = f'{from_s} > ('
137
+ orig = self.states[key]
138
+ if not orig:
139
+ raise Exception(f'from state not found for {key}')
140
+
141
+ orig.comeback_token = token
142
+ orig.comeback_state = to_s
143
+ if self.debug:
144
+ print(f'{from_s} > ) = {to_s}')
145
+ self.states[key] = orig
146
+
147
+ def traverse_tokens(self, tokens: list[str], state: State = State('')):
148
+ for token in tokens[:-1]:
149
+ if not token:
150
+ continue
151
+
152
+ if self.debug:
153
+ print(f'{token} ', end='')
154
+
155
+ last_name = None
156
+
157
+ if (t := token.lower()) in self.keywords():
158
+ token = t
159
+ elif token in ['*', ',', '.']:
160
+ pass
161
+ else:
162
+ last_name = token
163
+ token = 'word'
164
+
165
+ with log_exc():
166
+ context = state.context
167
+ state = self.states[f'{state.state} > {token}']
168
+ state.context = context
169
+
170
+ if last_name:
171
+ state.context['last_name'] = last_name
172
+
173
+ return state