kaqing 2.0.14__py3-none-any.whl → 2.0.145__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/apps.py +2 -2
- adam/batch.py +13 -3
- adam/checks/check_utils.py +4 -4
- adam/checks/compactionstats.py +1 -1
- adam/checks/cpu.py +2 -2
- adam/checks/disk.py +1 -1
- adam/checks/gossip.py +1 -1
- adam/checks/memory.py +3 -3
- adam/checks/status.py +1 -1
- adam/commands/alter_tables.py +81 -0
- adam/commands/app.py +3 -3
- adam/commands/app_ping.py +2 -2
- adam/commands/audit/audit.py +86 -0
- adam/commands/audit/audit_repair_tables.py +77 -0
- adam/commands/audit/audit_run.py +58 -0
- adam/commands/audit/show_last10.py +51 -0
- adam/commands/audit/show_slow10.py +50 -0
- adam/commands/audit/show_top10.py +48 -0
- adam/commands/audit/utils_show_top10.py +59 -0
- adam/commands/bash/__init__.py +0 -0
- adam/commands/bash/bash.py +133 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/cat.py +56 -0
- adam/commands/cd.py +12 -82
- adam/commands/check.py +6 -0
- adam/commands/cli_commands.py +3 -3
- adam/commands/code.py +60 -0
- adam/commands/command.py +48 -12
- adam/commands/commands_utils.py +4 -5
- adam/commands/cql/__init__.py +0 -0
- adam/commands/cql/cql_completions.py +28 -0
- adam/commands/cql/cql_utils.py +209 -0
- adam/commands/{cqlsh.py → cql/cqlsh.py} +15 -10
- adam/commands/deploy/code_utils.py +2 -2
- adam/commands/deploy/deploy.py +8 -21
- adam/commands/deploy/deploy_frontend.py +1 -1
- adam/commands/deploy/deploy_pg_agent.py +3 -3
- adam/commands/deploy/deploy_pod.py +28 -27
- adam/commands/deploy/deploy_utils.py +16 -26
- adam/commands/deploy/undeploy.py +8 -21
- adam/commands/deploy/undeploy_frontend.py +1 -1
- adam/commands/deploy/undeploy_pg_agent.py +5 -3
- adam/commands/deploy/undeploy_pod.py +12 -10
- adam/commands/devices/__init__.py +0 -0
- adam/commands/devices/device.py +27 -0
- adam/commands/devices/device_app.py +146 -0
- adam/commands/devices/device_auit_log.py +43 -0
- adam/commands/devices/device_cass.py +145 -0
- adam/commands/devices/device_export.py +86 -0
- adam/commands/devices/device_postgres.py +109 -0
- adam/commands/devices/devices.py +25 -0
- adam/commands/export/__init__.py +0 -0
- adam/commands/export/clean_up_export_session.py +53 -0
- adam/commands/export/clean_up_export_sessions.py +40 -0
- adam/commands/export/drop_export_database.py +58 -0
- adam/commands/export/drop_export_databases.py +46 -0
- adam/commands/export/export.py +83 -0
- adam/commands/export/export_databases.py +170 -0
- adam/commands/export/export_select.py +85 -0
- adam/commands/export/export_select_x.py +54 -0
- adam/commands/export/export_use.py +55 -0
- adam/commands/export/exporter.py +364 -0
- adam/commands/export/import_session.py +68 -0
- adam/commands/export/importer.py +67 -0
- adam/commands/export/importer_athena.py +80 -0
- adam/commands/export/importer_sqlite.py +47 -0
- adam/commands/export/show_column_counts.py +63 -0
- adam/commands/export/show_export_databases.py +39 -0
- adam/commands/export/show_export_session.py +51 -0
- adam/commands/export/show_export_sessions.py +47 -0
- adam/commands/export/utils_export.py +291 -0
- adam/commands/help.py +12 -7
- adam/commands/issues.py +6 -0
- adam/commands/kubectl.py +41 -0
- adam/commands/login.py +7 -4
- adam/commands/logs.py +2 -1
- adam/commands/ls.py +4 -107
- adam/commands/medusa/medusa.py +2 -26
- adam/commands/medusa/medusa_backup.py +2 -2
- adam/commands/medusa/medusa_restore.py +3 -4
- adam/commands/medusa/medusa_show_backupjobs.py +4 -3
- adam/commands/medusa/medusa_show_restorejobs.py +3 -3
- adam/commands/nodetool.py +9 -4
- adam/commands/param_set.py +1 -1
- adam/commands/postgres/postgres.py +42 -43
- adam/commands/postgres/{postgres_session.py → postgres_context.py} +43 -42
- adam/commands/postgres/postgres_utils.py +31 -0
- adam/commands/postgres/psql_completions.py +10 -0
- adam/commands/preview_table.py +18 -40
- adam/commands/pwd.py +2 -28
- adam/commands/reaper/reaper.py +4 -24
- adam/commands/reaper/reaper_restart.py +1 -1
- adam/commands/reaper/reaper_session.py +2 -2
- adam/commands/repair/repair.py +3 -27
- adam/commands/repair/repair_log.py +1 -1
- adam/commands/repair/repair_run.py +2 -2
- adam/commands/repair/repair_scan.py +1 -1
- adam/commands/repair/repair_stop.py +1 -1
- adam/commands/report.py +6 -0
- adam/commands/restart.py +2 -2
- adam/commands/rollout.py +1 -1
- adam/commands/show/show.py +11 -26
- adam/commands/show/show_app_actions.py +3 -0
- adam/commands/show/show_app_id.py +1 -1
- adam/commands/show/show_app_queues.py +3 -2
- adam/commands/show/show_cassandra_status.py +3 -3
- adam/commands/show/show_cassandra_version.py +3 -3
- adam/commands/show/show_host.py +33 -0
- adam/commands/show/show_login.py +3 -0
- adam/commands/show/show_processes.py +1 -1
- adam/commands/show/show_repairs.py +2 -2
- adam/commands/show/show_storage.py +1 -1
- adam/commands/watch.py +1 -1
- adam/config.py +16 -3
- adam/embedded_params.py +1 -1
- adam/pod_exec_result.py +10 -2
- adam/repl.py +127 -117
- adam/repl_commands.py +51 -16
- adam/repl_state.py +276 -55
- adam/sql/__init__.py +0 -0
- adam/sql/sql_completer.py +120 -0
- adam/sql/sql_state_machine.py +617 -0
- adam/sql/term_completer.py +76 -0
- adam/sso/authn_ad.py +1 -1
- adam/sso/cred_cache.py +1 -1
- adam/sso/idp.py +1 -1
- adam/utils.py +83 -2
- adam/utils_athena.py +145 -0
- adam/utils_audits.py +102 -0
- adam/utils_k8s/__init__.py +0 -0
- adam/utils_k8s/app_clusters.py +33 -0
- adam/utils_k8s/app_pods.py +31 -0
- adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -21
- adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
- adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
- adam/{k8s_utils → utils_k8s}/kube_context.py +1 -1
- adam/{k8s_utils → utils_k8s}/pods.py +119 -26
- adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
- adam/{k8s_utils → utils_k8s}/statefulsets.py +5 -4
- adam/utils_net.py +24 -0
- 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 +101 -0
- adam/version.py +1 -1
- {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/METADATA +1 -1
- kaqing-2.0.145.dist-info/RECORD +227 -0
- adam/commands/bash.py +0 -87
- adam/commands/cql_utils.py +0 -53
- adam/commands/devices.py +0 -89
- kaqing-2.0.14.dist-info/RECORD +0 -167
- /adam/{k8s_utils → commands/audit}/__init__.py +0 -0
- /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
- /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
- /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
- /adam/{k8s_utils → utils_k8s}/jobs.py +0 -0
- /adam/{k8s_utils → utils_k8s}/service_accounts.py +0 -0
- /adam/{k8s_utils → utils_k8s}/services.py +0 -0
- /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
- {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/WHEEL +0 -0
- {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/entry_points.txt +0 -0
- {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/top_level.txt +0 -0
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
3
|
+
from datetime import datetime
|
|
3
4
|
import sys
|
|
4
5
|
import time
|
|
5
6
|
from typing import TypeVar, cast
|
|
6
7
|
from kubernetes import client
|
|
7
8
|
from kubernetes.stream import stream
|
|
8
|
-
from kubernetes.stream.ws_client import ERROR_CHANNEL
|
|
9
|
+
from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
|
|
9
10
|
|
|
10
11
|
from adam.config import Config
|
|
11
|
-
from adam.
|
|
12
|
+
from adam.utils_k8s.volumes import ConfigMapMount
|
|
12
13
|
from adam.pod_exec_result import PodExecResult
|
|
13
14
|
from adam.utils import elapsed_time, log2
|
|
14
15
|
from .kube_context import KubeContext
|
|
15
16
|
|
|
17
|
+
from websocket._core import WebSocket
|
|
18
|
+
|
|
16
19
|
T = TypeVar('T')
|
|
17
20
|
_TEST_POD_EXEC_OUTS: PodExecResult = None
|
|
18
21
|
|
|
19
22
|
# utility collection on pods; methods are all static
|
|
20
23
|
class Pods:
|
|
24
|
+
_TEST_POD_CLOSE_SOCKET: bool = False
|
|
25
|
+
|
|
21
26
|
def set_test_pod_exec_outs(outs: PodExecResult):
|
|
22
27
|
global _TEST_POD_EXEC_OUTS
|
|
23
28
|
_TEST_POD_EXEC_OUTS = outs
|
|
@@ -27,7 +32,7 @@ class Pods:
|
|
|
27
32
|
def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
|
|
28
33
|
try:
|
|
29
34
|
v1 = client.CoreV1Api()
|
|
30
|
-
|
|
35
|
+
v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
|
|
31
36
|
except Exception as e:
|
|
32
37
|
log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
|
|
33
38
|
|
|
@@ -42,12 +47,16 @@ class Pods:
|
|
|
42
47
|
namespace: str,
|
|
43
48
|
body: Callable[[ThreadPoolExecutor, str, str, bool], T],
|
|
44
49
|
post: Callable[[T], T] = None,
|
|
45
|
-
action: str = 'action',
|
|
50
|
+
action: str = 'action',
|
|
51
|
+
max_workers=0,
|
|
52
|
+
show_out=True,
|
|
53
|
+
on_any = False,
|
|
54
|
+
background = False) -> list[T]:
|
|
46
55
|
show_out = KubeContext.show_out(show_out)
|
|
47
56
|
|
|
48
57
|
if not max_workers:
|
|
49
58
|
max_workers = Config().action_workers(action, 0)
|
|
50
|
-
if max_workers > 0:
|
|
59
|
+
if not on_any and max_workers > 0:
|
|
51
60
|
# if parallel, node sampling is suppressed
|
|
52
61
|
if KubeContext.show_parallelism():
|
|
53
62
|
log2(f'Executing on all nodes from statefulset in parallel...')
|
|
@@ -70,7 +79,7 @@ class Pods:
|
|
|
70
79
|
else:
|
|
71
80
|
results: list[T] = []
|
|
72
81
|
|
|
73
|
-
samples = Config().action_node_samples(action, sys.maxsize)
|
|
82
|
+
samples = 1 if on_any else Config().action_node_samples(action, sys.maxsize)
|
|
74
83
|
l = min(len(pods), samples)
|
|
75
84
|
adj = 'all'
|
|
76
85
|
if l < len(pods):
|
|
@@ -79,7 +88,8 @@ class Pods:
|
|
|
79
88
|
log2(f'Executing on {adj} nodes from statefulset...')
|
|
80
89
|
for pod_name in pods:
|
|
81
90
|
try:
|
|
82
|
-
|
|
91
|
+
# disable stdout from the pod_exec, then show the output in a for loop
|
|
92
|
+
result = body(None, pod_name, namespace, False)
|
|
83
93
|
if post:
|
|
84
94
|
result = post(result, show_out=show_out)
|
|
85
95
|
results.append(result)
|
|
@@ -92,7 +102,12 @@ class Pods:
|
|
|
92
102
|
|
|
93
103
|
return results
|
|
94
104
|
|
|
95
|
-
def exec(pod_name: str, container: str, namespace: str, command: str,
|
|
105
|
+
def exec(pod_name: str, container: str, namespace: str, command: str,
|
|
106
|
+
show_out = True, throw_err = False, shell = '/bin/sh',
|
|
107
|
+
background = False,
|
|
108
|
+
log_file = None,
|
|
109
|
+
interaction: Callable[[any, list[str]], any] = None,
|
|
110
|
+
env_prefix: str = None):
|
|
96
111
|
if _TEST_POD_EXEC_OUTS:
|
|
97
112
|
return _TEST_POD_EXEC_OUTS
|
|
98
113
|
|
|
@@ -100,12 +115,33 @@ class Pods:
|
|
|
100
115
|
|
|
101
116
|
api = client.CoreV1Api()
|
|
102
117
|
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
tty = True
|
|
119
|
+
exec_command = [shell, '-c', command]
|
|
120
|
+
if env_prefix:
|
|
121
|
+
exec_command = [shell, '-c', f'{env_prefix} {command}']
|
|
122
|
+
|
|
123
|
+
if background or command.endswith(' &'):
|
|
124
|
+
# should be false for starting a background process
|
|
125
|
+
tty = False
|
|
126
|
+
|
|
127
|
+
if Config().get('repl.background-process.auto-nohup', True):
|
|
128
|
+
command = command.strip(' &')
|
|
129
|
+
cmd_name = ''
|
|
130
|
+
if command.startswith('nodetool '):
|
|
131
|
+
cmd_name = f".{'_'.join(command.split(' ')[5:])}"
|
|
132
|
+
|
|
133
|
+
if not log_file:
|
|
134
|
+
log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
|
|
135
|
+
command = f"nohup {command} > {log_file} 2>&1 &"
|
|
136
|
+
if env_prefix:
|
|
137
|
+
command = f'{env_prefix} {command}'
|
|
138
|
+
exec_command = [shell, '-c', command]
|
|
139
|
+
|
|
140
|
+
k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
|
|
105
141
|
if show_out:
|
|
106
142
|
print(k_command)
|
|
107
143
|
|
|
108
|
-
resp = stream(
|
|
144
|
+
resp: WSClient = stream(
|
|
109
145
|
api.connect_get_namespaced_pod_exec,
|
|
110
146
|
pod_name,
|
|
111
147
|
namespace,
|
|
@@ -114,10 +150,11 @@ class Pods:
|
|
|
114
150
|
stderr=True,
|
|
115
151
|
stdin=True,
|
|
116
152
|
stdout=True,
|
|
117
|
-
tty=
|
|
153
|
+
tty=tty,
|
|
118
154
|
_preload_content=False,
|
|
119
155
|
)
|
|
120
156
|
|
|
157
|
+
s: WebSocket = resp.sock
|
|
121
158
|
stdout = []
|
|
122
159
|
stderr = []
|
|
123
160
|
error_output = None
|
|
@@ -139,7 +176,7 @@ class Pods:
|
|
|
139
176
|
try:
|
|
140
177
|
# get the exit code from server
|
|
141
178
|
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
142
|
-
except Exception:
|
|
179
|
+
except Exception as e:
|
|
143
180
|
pass
|
|
144
181
|
except Exception as e:
|
|
145
182
|
if throw_err:
|
|
@@ -148,9 +185,49 @@ class Pods:
|
|
|
148
185
|
log2(e)
|
|
149
186
|
finally:
|
|
150
187
|
resp.close()
|
|
188
|
+
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
189
|
+
try:
|
|
190
|
+
s.sock.close()
|
|
191
|
+
except:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
|
|
151
195
|
|
|
152
|
-
|
|
196
|
+
def read_file(pod_name: str, container: str, namespace: str, file_path: str):
|
|
197
|
+
v1 = client.CoreV1Api()
|
|
153
198
|
|
|
199
|
+
resp = stream(
|
|
200
|
+
v1.connect_get_namespaced_pod_exec,
|
|
201
|
+
name=pod_name,
|
|
202
|
+
namespace=namespace,
|
|
203
|
+
container=container,
|
|
204
|
+
command=["cat", file_path],
|
|
205
|
+
stderr=True, stdin=False,
|
|
206
|
+
stdout=True, tty=False,
|
|
207
|
+
_preload_content=False, # Important for streaming
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
s: WebSocket = resp.sock
|
|
211
|
+
try:
|
|
212
|
+
while resp.is_open():
|
|
213
|
+
resp.update(timeout=1)
|
|
214
|
+
if resp.peek_stdout():
|
|
215
|
+
yield resp.read_stdout()
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
# get the exit code from server
|
|
219
|
+
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
pass
|
|
222
|
+
except Exception as e:
|
|
223
|
+
raise e
|
|
224
|
+
finally:
|
|
225
|
+
resp.close()
|
|
226
|
+
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
227
|
+
try:
|
|
228
|
+
s.sock.close()
|
|
229
|
+
except:
|
|
230
|
+
pass
|
|
154
231
|
def get_container(namespace: str, pod_name: str, container_name: str):
|
|
155
232
|
pod = Pods.get(namespace, pod_name)
|
|
156
233
|
if not pod:
|
|
@@ -233,17 +310,33 @@ class Pods:
|
|
|
233
310
|
))
|
|
234
311
|
)
|
|
235
312
|
|
|
236
|
-
def wait_for_running(namespace: str, pod_name: str, msg: str=None, label_selector: str = None):
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
313
|
+
def wait_for_running(namespace: str, pod_name: str, msg: str = None, label_selector: str = None):
|
|
314
|
+
cnt = 2
|
|
315
|
+
while (cnt < 302 and Pods.get_with_selector(namespace, label_selector) if label_selector else Pods.get(namespace, pod_name)).status.phase != 'Running':
|
|
316
|
+
if not msg:
|
|
317
|
+
msg = f'Waiting for the {pod_name} pod to start up.'
|
|
318
|
+
|
|
319
|
+
max_len = len(msg) + 3
|
|
320
|
+
mod = cnt % 3
|
|
321
|
+
padded = ''
|
|
322
|
+
if mod == 0:
|
|
323
|
+
padded = f'\r{msg}'.ljust(max_len)
|
|
324
|
+
elif mod == 1:
|
|
325
|
+
padded = f'\r{msg}.'.ljust(max_len)
|
|
326
|
+
else:
|
|
327
|
+
padded = f'\r{msg}..'.ljust(max_len)
|
|
328
|
+
log2(padded, nl=False)
|
|
329
|
+
cnt += 1
|
|
330
|
+
time.sleep(1)
|
|
331
|
+
|
|
332
|
+
log2(f'\r{msg}..'.ljust(max_len), nl=False)
|
|
333
|
+
if cnt < 302:
|
|
334
|
+
log2(' OK')
|
|
335
|
+
else:
|
|
336
|
+
log2(' Timed Out')
|
|
247
337
|
|
|
248
338
|
def completed(namespace: str, pod_name: str):
|
|
249
|
-
return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
|
|
339
|
+
return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
|
|
340
|
+
|
|
341
|
+
def log_prefix():
|
|
342
|
+
return Config().get('log-prefix', '/tmp/qing')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import functools
|
|
2
3
|
import re
|
|
3
4
|
from typing import cast
|
|
4
5
|
from kubernetes import client
|
|
@@ -9,7 +10,10 @@ from adam.utils import log2
|
|
|
9
10
|
|
|
10
11
|
# utility collection on secrets; methods are all static
|
|
11
12
|
class Secrets:
|
|
13
|
+
@functools.lru_cache()
|
|
12
14
|
def list_secrets(namespace: str = None, name_pattern: str = None):
|
|
15
|
+
Config().wait_log('Inspecting Cassandra Instances...')
|
|
16
|
+
|
|
13
17
|
secrets_names = []
|
|
14
18
|
|
|
15
19
|
v1 = client.CoreV1Api()
|
|
@@ -24,11 +24,12 @@ class StatefulSets:
|
|
|
24
24
|
|
|
25
25
|
return statefulsets.items
|
|
26
26
|
|
|
27
|
+
@functools.lru_cache()
|
|
27
28
|
def list_sts_name_and_ns():
|
|
28
29
|
return [(statefulset.metadata.name, statefulset.metadata.namespace) for statefulset in StatefulSets.list_sts()]
|
|
29
30
|
|
|
30
|
-
def list_sts_names(
|
|
31
|
-
if
|
|
31
|
+
def list_sts_names():
|
|
32
|
+
if not KubeContext.in_cluster_namespace():
|
|
32
33
|
return [f"{sts}@{ns}" for sts, ns in StatefulSets.list_sts_name_and_ns()]
|
|
33
34
|
else:
|
|
34
35
|
return [f"{sts}" for sts, _ in StatefulSets.list_sts_name_and_ns()]
|
|
@@ -61,10 +62,10 @@ class StatefulSets:
|
|
|
61
62
|
namespace: str,
|
|
62
63
|
body: Callable[[ThreadPoolExecutor, str, str, bool], T],
|
|
63
64
|
post: Callable[[T], T] = None,
|
|
64
|
-
action: str = 'action', max_workers=0, show_out=True) -> list[T]:
|
|
65
|
+
action: str = 'action', max_workers=0, show_out=True, on_any = False, background = False) -> list[T]:
|
|
65
66
|
pods = StatefulSets.pod_names(statefulset, namespace)
|
|
66
67
|
|
|
67
|
-
return Pods.on_pods(pods, namespace, body, post=post, action=action, max_workers=max_workers, show_out=show_out)
|
|
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)
|
|
68
69
|
|
|
69
70
|
@functools.lru_cache()
|
|
70
71
|
def pod_names(ss: str, ns: str):
|
adam/utils_net.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
MY_HOST = None
|
|
4
|
+
|
|
5
|
+
def get_my_host():
|
|
6
|
+
global MY_HOST
|
|
7
|
+
|
|
8
|
+
if MY_HOST:
|
|
9
|
+
return MY_HOST
|
|
10
|
+
|
|
11
|
+
MY_HOST = get_ip_from_hostname('host.docker.internal')
|
|
12
|
+
if not MY_HOST:
|
|
13
|
+
MY_HOST = socket.gethostname()
|
|
14
|
+
|
|
15
|
+
if not MY_HOST:
|
|
16
|
+
MY_HOST = 'NA'
|
|
17
|
+
|
|
18
|
+
return MY_HOST
|
|
19
|
+
|
|
20
|
+
def get_ip_from_hostname(hostname):
|
|
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
|
+
__all__ = [
|
|
5
|
+
'State',
|
|
6
|
+
'StateMachine',
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T')
|
|
10
|
+
|
|
11
|
+
class State:
|
|
12
|
+
def __init__(self, state: str, comeback_token: str = None, comeback_state: str = None):
|
|
13
|
+
self.state = state
|
|
14
|
+
self.comeback_token = comeback_token
|
|
15
|
+
self.comeback_state = comeback_state
|
|
16
|
+
self.context: dict[str, str] = {}
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return f'{self.state if self.state else None} comeback[{self.comeback_token} {self.comeback_state}]'
|
|
20
|
+
|
|
21
|
+
class StateMachine(Generic[T]):
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def spec(self) -> str:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def keywords(self) -> list[str]:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def expandable_names(self):
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
def incomplete_name_transition_condition(self, from_s: str, token: str, to_s: str, suggestions: str) -> list[str]:
|
|
34
|
+
if not suggestions:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
tokens = [token]
|
|
38
|
+
if '|' in token:
|
|
39
|
+
tokens = token.split('|')
|
|
40
|
+
|
|
41
|
+
if 'name' not in tokens:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
return tokens
|
|
45
|
+
|
|
46
|
+
def __init__(self, indent=0, push_level = 0, debug = False):
|
|
47
|
+
self.states: dict[str, State] = {}
|
|
48
|
+
self.suggestions: dict[str, str] = {}
|
|
49
|
+
|
|
50
|
+
self.indent = indent
|
|
51
|
+
self.push_level = push_level
|
|
52
|
+
self.comebacks: dict[int, str] = {}
|
|
53
|
+
self.debug = debug
|
|
54
|
+
|
|
55
|
+
from_ss_to_add = []
|
|
56
|
+
from_ss = ['']
|
|
57
|
+
words: str = None
|
|
58
|
+
for l in self.spec():
|
|
59
|
+
t_and_w = l.split('^')
|
|
60
|
+
if len(t_and_w) > 1:
|
|
61
|
+
words = t_and_w[1].strip()
|
|
62
|
+
else:
|
|
63
|
+
words = None
|
|
64
|
+
|
|
65
|
+
tks = t_and_w[0].strip(' ').split('>')
|
|
66
|
+
if not l.startswith('-'):
|
|
67
|
+
if words:
|
|
68
|
+
self.suggestions[tks[0].strip(' ')] = words
|
|
69
|
+
|
|
70
|
+
if len(tks) == 1:
|
|
71
|
+
from_ss_to_add.append(tks[0].strip(' '))
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
from_ss = []
|
|
75
|
+
from_ss.extend(from_ss_to_add)
|
|
76
|
+
from_ss_to_add = []
|
|
77
|
+
from_ss.append(tks[0].strip(' '))
|
|
78
|
+
|
|
79
|
+
self.add_transitions(from_ss, tks, words)
|
|
80
|
+
|
|
81
|
+
def add_transitions(self, from_ss: list[str], tks: list[str], words: str):
|
|
82
|
+
token = tks[1].strip(' ')
|
|
83
|
+
if len(tks) > 2:
|
|
84
|
+
to_s = tks[2].strip(' ')
|
|
85
|
+
for from_s in from_ss:
|
|
86
|
+
self.add_whitespace_transition(from_s, to_s)
|
|
87
|
+
self.add_transition(from_s, token, to_s)
|
|
88
|
+
self.add_incomplete_name_transition(from_s, token, to_s, words)
|
|
89
|
+
elif '<' in tks[0]:
|
|
90
|
+
from_and_token = tks[0].split('<')
|
|
91
|
+
if len(from_and_token) > 1:
|
|
92
|
+
for from_s in from_ss:
|
|
93
|
+
self.add_comeback_transition(from_s, from_and_token[1], tks[1].strip(' '))
|
|
94
|
+
|
|
95
|
+
def add_whitespace_transition(self, from_s: str, to_s: str):
|
|
96
|
+
if self.witespace_transition_condition(from_s, to_s):
|
|
97
|
+
if self.debug:
|
|
98
|
+
print(f'{from_s[:-1]} > _ = {to_s}')
|
|
99
|
+
self.states[f'{from_s[:-1]} > _'] = State(from_s)
|
|
100
|
+
|
|
101
|
+
def witespace_transition_condition(self, from_s: str, to_s: str):
|
|
102
|
+
return from_s.endswith('_')
|
|
103
|
+
|
|
104
|
+
def add_incomplete_name_transition(self, from_s: str, token: str, to_s: str, words: str):
|
|
105
|
+
if tokens := self.incomplete_name_transition_condition(from_s, token, to_s, words):
|
|
106
|
+
self.suggestions[to_s] = words
|
|
107
|
+
for token in tokens:
|
|
108
|
+
if self.debug:
|
|
109
|
+
print(f'{to_s} > {token} = {to_s}')
|
|
110
|
+
self.states[f'{to_s} > {token}'] = State(to_s)
|
|
111
|
+
|
|
112
|
+
def add_transition(self, from_s: str, token: str, to_s: str):
|
|
113
|
+
tokens = [token]
|
|
114
|
+
if '|' in token:
|
|
115
|
+
tokens = token.split('|')
|
|
116
|
+
|
|
117
|
+
for t in tokens:
|
|
118
|
+
if t == '_or_':
|
|
119
|
+
t = '||'
|
|
120
|
+
elif t == 'pipe':
|
|
121
|
+
t = '|'
|
|
122
|
+
elif t == '_rdr0_':
|
|
123
|
+
t = '<'
|
|
124
|
+
elif t == '_rdr1_':
|
|
125
|
+
t = '>'
|
|
126
|
+
elif t == '_rdr2_':
|
|
127
|
+
t = '2>'
|
|
128
|
+
|
|
129
|
+
if self.debug:
|
|
130
|
+
print(f'{from_s} > {t} = {to_s}')
|
|
131
|
+
self.states[f'{from_s} > {t}'] = State(to_s)
|
|
132
|
+
|
|
133
|
+
def add_comeback_transition(self, from_s: str, token: str, to_s: str):
|
|
134
|
+
key = f'{from_s} > ('
|
|
135
|
+
orig = self.states[key]
|
|
136
|
+
if not orig:
|
|
137
|
+
raise Exception(f'from state not found for {key}')
|
|
138
|
+
|
|
139
|
+
orig.comeback_token = token
|
|
140
|
+
orig.comeback_state = to_s
|
|
141
|
+
if self.debug:
|
|
142
|
+
print(f'{from_s} > ) = {to_s}')
|
|
143
|
+
self.states[key] = orig
|
|
144
|
+
|
|
145
|
+
def traverse_tokens(self, tokens: list[str], state: State = State('')):
|
|
146
|
+
for token in tokens[:-1]:
|
|
147
|
+
if not token:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if self.debug:
|
|
151
|
+
print(f'{token} ', end='')
|
|
152
|
+
|
|
153
|
+
last_name = None
|
|
154
|
+
|
|
155
|
+
if (t := token.lower()) in self.keywords():
|
|
156
|
+
token = t
|
|
157
|
+
elif token in ['*', ',', '.']:
|
|
158
|
+
pass
|
|
159
|
+
else:
|
|
160
|
+
last_name = token
|
|
161
|
+
token = 'word'
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
context = state.context
|
|
165
|
+
state = self.states[f'{state.state} > {token}']
|
|
166
|
+
state.context = context
|
|
167
|
+
|
|
168
|
+
if last_name:
|
|
169
|
+
state.context['last_name'] = last_name
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
return state
|
adam/utils_sqlite.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import glob
|
|
3
|
+
import os
|
|
4
|
+
import sqlite3
|
|
5
|
+
import pandas
|
|
6
|
+
|
|
7
|
+
from adam.config import Config
|
|
8
|
+
from adam.utils import lines_to_tabular, log
|
|
9
|
+
|
|
10
|
+
# no state utility class
|
|
11
|
+
class SQLite:
|
|
12
|
+
def local_db_dir():
|
|
13
|
+
return Config().get('export.sqlite.local-db-dir', '/tmp/qing-db')
|
|
14
|
+
|
|
15
|
+
def keyspace(database: str):
|
|
16
|
+
return '_'.join(database.replace(".db", "").split('_')[1:])
|
|
17
|
+
|
|
18
|
+
def connect(session: str):
|
|
19
|
+
os.makedirs(SQLite.local_db_dir(), exist_ok=True)
|
|
20
|
+
|
|
21
|
+
conn = None
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{session}_root.db')
|
|
25
|
+
cursor = None
|
|
26
|
+
try:
|
|
27
|
+
cursor = conn.cursor()
|
|
28
|
+
for d in SQLite.database_names(session):
|
|
29
|
+
if d != f'{session}_root.db':
|
|
30
|
+
q = f"ATTACH DATABASE '{SQLite.local_db_dir()}/{d}' AS {SQLite.keyspace(d)};"
|
|
31
|
+
cursor.execute(q)
|
|
32
|
+
finally:
|
|
33
|
+
if cursor:
|
|
34
|
+
cursor.close()
|
|
35
|
+
finally:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
return conn
|
|
39
|
+
|
|
40
|
+
@functools.lru_cache()
|
|
41
|
+
def database_names(prefix: str = None):
|
|
42
|
+
Config().wait_log('Inspecting export databases...')
|
|
43
|
+
|
|
44
|
+
pattern = f'{SQLite.local_db_dir()}/s*.db'
|
|
45
|
+
if prefix:
|
|
46
|
+
pattern = f'{SQLite.local_db_dir()}/{prefix}*'
|
|
47
|
+
return [os.path.basename(f) for f in glob.glob(pattern)]
|
|
48
|
+
|
|
49
|
+
def clear_cache(cache: str = None):
|
|
50
|
+
SQLite.database_names.cache_clear()
|
|
51
|
+
SQLite.table_names.cache_clear()
|
|
52
|
+
|
|
53
|
+
@functools.lru_cache()
|
|
54
|
+
def table_names(database: str):
|
|
55
|
+
tokens = database.replace('.db', '').split('_')
|
|
56
|
+
ts_prefix = tokens[0]
|
|
57
|
+
keyspace = '_'.join(tokens[1:])
|
|
58
|
+
|
|
59
|
+
conn = None
|
|
60
|
+
tables = []
|
|
61
|
+
try:
|
|
62
|
+
conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{ts_prefix}_{keyspace}.db')
|
|
63
|
+
cursor = None
|
|
64
|
+
try:
|
|
65
|
+
cursor = conn.cursor()
|
|
66
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
|
|
67
|
+
|
|
68
|
+
tables = [row[0] for row in cursor.fetchall() if row[0] != "sqlite_sequence"]
|
|
69
|
+
finally:
|
|
70
|
+
if cursor:
|
|
71
|
+
cursor.close()
|
|
72
|
+
|
|
73
|
+
return tables
|
|
74
|
+
except sqlite3.Error as e:
|
|
75
|
+
print(f"Error connecting to or querying the database: {e}")
|
|
76
|
+
return []
|
|
77
|
+
finally:
|
|
78
|
+
if conn:
|
|
79
|
+
conn.close()
|
|
80
|
+
|
|
81
|
+
@functools.lru_cache()
|
|
82
|
+
def column_names(tables: list[str] = [], database: str = None, function: str = 'audit', partition_cols_only = False):
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def run_query(query: str, database: str = None, conn_passed = None):
|
|
86
|
+
conn = None
|
|
87
|
+
try:
|
|
88
|
+
if not conn_passed:
|
|
89
|
+
conn = SQLite.connect(database)
|
|
90
|
+
|
|
91
|
+
df = SQLite.query(conn_passed if conn_passed else conn, query)
|
|
92
|
+
lines = ['\t'.join(map(str, line)) for line in df.values.tolist()]
|
|
93
|
+
log(lines_to_tabular(lines, header='\t'.join(df.columns.tolist()), separator='\t'))
|
|
94
|
+
|
|
95
|
+
return len(lines)
|
|
96
|
+
finally:
|
|
97
|
+
if conn:
|
|
98
|
+
conn.close()
|
|
99
|
+
|
|
100
|
+
def query(conn, sql: str) -> tuple[str, str, list]:
|
|
101
|
+
return pandas.read_sql_query(sql, conn)
|