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.
- adam/__init__.py +0 -2
- adam/app_session.py +8 -11
- adam/batch.py +3 -3
- adam/checks/check_utils.py +14 -46
- adam/checks/cpu.py +7 -1
- adam/checks/cpu_metrics.py +52 -0
- adam/checks/disk.py +2 -3
- adam/columns/columns.py +3 -1
- adam/columns/cpu.py +3 -1
- adam/columns/cpu_metrics.py +22 -0
- adam/columns/memory.py +3 -4
- adam/commands/__init__.py +18 -0
- adam/commands/alter_tables.py +43 -47
- adam/commands/audit/audit.py +24 -25
- adam/commands/audit/audit_repair_tables.py +14 -17
- adam/commands/audit/audit_run.py +15 -23
- adam/commands/audit/show_last10.py +10 -13
- adam/commands/audit/show_slow10.py +10 -13
- adam/commands/audit/show_top10.py +10 -14
- adam/commands/audit/utils_show_top10.py +2 -3
- adam/commands/bash/__init__.py +5 -0
- adam/commands/bash/bash.py +8 -96
- adam/commands/bash/utils_bash.py +16 -0
- adam/commands/cat.py +14 -19
- adam/commands/cd.py +12 -100
- adam/commands/check.py +20 -21
- adam/commands/cli_commands.py +2 -3
- adam/commands/code.py +20 -23
- adam/commands/command.py +123 -39
- adam/commands/commands_utils.py +8 -17
- adam/commands/cp.py +33 -39
- adam/commands/cql/cql_completions.py +28 -10
- adam/commands/cql/cqlsh.py +10 -30
- adam/commands/cql/utils_cql.py +343 -0
- adam/commands/deploy/code_start.py +7 -10
- adam/commands/deploy/code_stop.py +4 -21
- adam/commands/deploy/code_utils.py +3 -3
- adam/commands/deploy/deploy.py +4 -27
- adam/commands/deploy/deploy_frontend.py +14 -17
- adam/commands/deploy/deploy_pg_agent.py +2 -5
- adam/commands/deploy/deploy_pod.py +65 -73
- adam/commands/deploy/deploy_utils.py +14 -24
- adam/commands/deploy/undeploy.py +4 -27
- adam/commands/deploy/undeploy_frontend.py +4 -7
- adam/commands/deploy/undeploy_pg_agent.py +5 -7
- adam/commands/deploy/undeploy_pod.py +11 -12
- adam/commands/devices/__init__.py +0 -0
- adam/commands/devices/device.py +118 -0
- adam/commands/devices/device_app.py +173 -0
- adam/commands/devices/device_auit_log.py +49 -0
- adam/commands/devices/device_cass.py +185 -0
- adam/commands/devices/device_export.py +86 -0
- adam/commands/devices/device_postgres.py +144 -0
- adam/commands/devices/devices.py +25 -0
- adam/commands/exit.py +1 -4
- adam/commands/export/clean_up_all_export_sessions.py +37 -0
- adam/commands/export/clean_up_export_sessions.py +51 -0
- adam/commands/export/drop_export_database.py +55 -0
- adam/commands/export/drop_export_databases.py +43 -0
- adam/commands/export/export.py +19 -26
- adam/commands/export/export_databases.py +174 -0
- adam/commands/export/export_handlers.py +71 -0
- adam/commands/export/export_select.py +48 -22
- adam/commands/export/export_select_x.py +54 -0
- adam/commands/export/export_use.py +19 -23
- adam/commands/export/exporter.py +353 -0
- adam/commands/export/import_session.py +40 -0
- adam/commands/export/importer.py +67 -0
- adam/commands/export/importer_athena.py +77 -0
- adam/commands/export/importer_sqlite.py +39 -0
- adam/commands/export/show_column_counts.py +54 -0
- adam/commands/export/show_export_databases.py +36 -0
- adam/commands/export/show_export_session.py +48 -0
- adam/commands/export/show_export_sessions.py +44 -0
- adam/commands/export/utils_export.py +223 -162
- adam/commands/help.py +1 -1
- adam/commands/intermediate_command.py +49 -0
- adam/commands/issues.py +11 -43
- adam/commands/kubectl.py +3 -6
- adam/commands/login.py +22 -24
- adam/commands/logs.py +3 -6
- adam/commands/ls.py +11 -128
- adam/commands/medusa/medusa.py +4 -22
- adam/commands/medusa/medusa_backup.py +20 -24
- adam/commands/medusa/medusa_restore.py +29 -33
- adam/commands/medusa/medusa_show_backupjobs.py +14 -18
- adam/commands/medusa/medusa_show_restorejobs.py +11 -18
- adam/commands/nodetool.py +6 -15
- adam/commands/param_get.py +11 -12
- adam/commands/param_set.py +9 -10
- adam/commands/postgres/postgres.py +41 -34
- adam/commands/postgres/postgres_context.py +57 -24
- adam/commands/postgres/postgres_ls.py +4 -8
- adam/commands/postgres/postgres_preview.py +5 -9
- adam/commands/postgres/psql_completions.py +1 -1
- adam/commands/postgres/utils_postgres.py +66 -0
- adam/commands/preview_table.py +5 -44
- adam/commands/pwd.py +14 -47
- adam/commands/reaper/reaper.py +4 -27
- adam/commands/reaper/reaper_forward.py +48 -55
- adam/commands/reaper/reaper_forward_session.py +6 -0
- adam/commands/reaper/reaper_forward_stop.py +10 -16
- adam/commands/reaper/reaper_restart.py +7 -14
- adam/commands/reaper/reaper_run_abort.py +11 -30
- adam/commands/reaper/reaper_runs.py +42 -57
- adam/commands/reaper/reaper_runs_abort.py +29 -49
- adam/commands/reaper/reaper_schedule_activate.py +11 -30
- adam/commands/reaper/reaper_schedule_start.py +10 -29
- adam/commands/reaper/reaper_schedule_stop.py +10 -29
- adam/commands/reaper/reaper_schedules.py +4 -14
- adam/commands/reaper/reaper_status.py +8 -16
- adam/commands/reaper/utils_reaper.py +196 -0
- adam/commands/repair/repair.py +4 -22
- adam/commands/repair/repair_log.py +5 -11
- adam/commands/repair/repair_run.py +27 -34
- adam/commands/repair/repair_scan.py +32 -38
- adam/commands/repair/repair_stop.py +5 -11
- adam/commands/report.py +27 -29
- adam/commands/restart.py +25 -26
- adam/commands/rollout.py +19 -24
- adam/commands/shell.py +10 -4
- adam/commands/show/show.py +10 -25
- adam/commands/show/show_cassandra_repairs.py +35 -0
- adam/commands/show/show_cassandra_status.py +32 -43
- adam/commands/show/show_cassandra_version.py +5 -18
- adam/commands/show/show_commands.py +19 -24
- adam/commands/show/show_host.py +1 -1
- adam/commands/show/show_login.py +20 -27
- adam/commands/show/show_processes.py +15 -19
- adam/commands/show/show_storage.py +10 -20
- adam/commands/watch.py +26 -29
- adam/config.py +5 -14
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/pod_exec_result.py +3 -3
- adam/repl.py +40 -103
- adam/repl_commands.py +32 -16
- adam/repl_state.py +57 -28
- adam/sql/sql_completer.py +44 -28
- adam/sql/sql_state_machine.py +89 -28
- adam/sso/authn_ad.py +6 -8
- adam/sso/authn_okta.py +4 -6
- adam/sso/cred_cache.py +3 -5
- adam/sso/idp.py +9 -12
- adam/utils.py +435 -6
- adam/utils_athena.py +57 -37
- adam/utils_audits.py +12 -14
- adam/utils_issues.py +32 -0
- adam/utils_k8s/app_clusters.py +13 -18
- adam/utils_k8s/app_pods.py +2 -0
- adam/utils_k8s/cassandra_clusters.py +22 -19
- adam/utils_k8s/cassandra_nodes.py +2 -2
- adam/utils_k8s/custom_resources.py +16 -17
- adam/utils_k8s/ingresses.py +2 -2
- adam/utils_k8s/jobs.py +7 -11
- adam/utils_k8s/k8s.py +87 -0
- adam/utils_k8s/pods.py +40 -77
- adam/utils_k8s/secrets.py +4 -4
- adam/utils_k8s/service_accounts.py +5 -4
- adam/utils_k8s/services.py +2 -2
- adam/utils_k8s/statefulsets.py +1 -12
- adam/utils_net.py +4 -4
- adam/utils_repl/__init__.py +0 -0
- adam/utils_repl/automata_completer.py +48 -0
- adam/utils_repl/repl_completer.py +46 -0
- adam/utils_repl/state_machine.py +173 -0
- adam/utils_sqlite.py +137 -0
- adam/version.py +1 -1
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/METADATA +1 -1
- kaqing-2.0.172.dist-info/RECORD +230 -0
- adam/commands/app.py +0 -67
- adam/commands/app_ping.py +0 -44
- adam/commands/cql/cql_utils.py +0 -204
- adam/commands/devices.py +0 -147
- adam/commands/export/export_on_x.py +0 -76
- adam/commands/export/export_rmdbs.py +0 -65
- adam/commands/postgres/postgres_utils.py +0 -31
- adam/commands/reaper/reaper_session.py +0 -159
- adam/commands/show/show_app_actions.py +0 -56
- adam/commands/show/show_app_id.py +0 -47
- adam/commands/show/show_app_queues.py +0 -45
- adam/commands/show/show_repairs.py +0 -47
- adam/utils_export.py +0 -42
- kaqing-2.0.115.dist-info/RECORD +0 -203
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/WHEEL +0 -0
- {kaqing-2.0.115.dist-info → kaqing-2.0.172.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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'(.*?-.*?)-.*'),
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
adam/utils_k8s/services.py
CHANGED
|
@@ -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
|
-
|
|
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:
|
adam/utils_k8s/statefulsets.py
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|