kaqing 2.0.145__py3-none-any.whl → 2.0.189__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 +9 -12
- adam/apps.py +18 -4
- adam/batch.py +4 -4
- adam/checks/check_utils.py +16 -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 +24 -0
- adam/commands/alter_tables.py +33 -48
- adam/commands/app/__init__.py +0 -0
- adam/commands/app/app.py +38 -0
- adam/commands/{app_ping.py → app/app_ping.py} +7 -13
- adam/commands/app/show_app_actions.py +49 -0
- adam/commands/{show → app}/show_app_id.py +8 -11
- adam/commands/{show → app}/show_app_queues.py +7 -14
- adam/commands/app/utils_app.py +106 -0
- adam/commands/audit/audit.py +21 -40
- adam/commands/audit/audit_repair_tables.py +14 -19
- adam/commands/audit/audit_run.py +14 -22
- adam/commands/audit/completions_l.py +15 -0
- adam/commands/audit/show_last10.py +4 -19
- adam/commands/audit/show_slow10.py +4 -18
- adam/commands/audit/show_top10.py +4 -16
- adam/commands/audit/utils_show_top10.py +15 -3
- adam/commands/bash/__init__.py +5 -0
- adam/commands/bash/bash.py +7 -104
- adam/commands/bash/utils_bash.py +16 -0
- adam/commands/cat.py +7 -27
- adam/commands/cd.py +7 -11
- adam/commands/check.py +15 -24
- adam/commands/cli_commands.py +8 -4
- adam/commands/clipboard_copy.py +87 -0
- adam/commands/code.py +21 -24
- adam/commands/command.py +207 -42
- adam/commands/commands_utils.py +25 -27
- adam/commands/cql/completions_c.py +28 -0
- adam/commands/cql/cqlsh.py +9 -33
- adam/commands/cql/{cql_utils.py → utils_cql.py} +111 -15
- 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 +3 -6
- adam/commands/deploy/deploy_pod.py +64 -68
- adam/commands/deploy/undeploy.py +4 -27
- adam/commands/deploy/undeploy_frontend.py +4 -7
- adam/commands/deploy/undeploy_pg_agent.py +5 -8
- adam/commands/deploy/undeploy_pod.py +9 -12
- adam/commands/devices/device.py +124 -2
- adam/commands/devices/device_app.py +41 -24
- adam/commands/devices/device_auit_log.py +10 -4
- adam/commands/devices/device_cass.py +48 -14
- adam/commands/devices/device_export.py +13 -12
- adam/commands/devices/device_postgres.py +105 -54
- adam/commands/download_file.py +47 -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 +9 -10
- adam/commands/export/completions_x.py +11 -0
- adam/commands/export/download_export_session.py +40 -0
- adam/commands/export/drop_export_database.py +7 -26
- adam/commands/export/drop_export_databases.py +5 -14
- adam/commands/export/export.py +6 -52
- adam/commands/export/export_databases.py +108 -32
- adam/commands/export/export_select.py +8 -59
- adam/commands/export/export_sessions.py +209 -0
- adam/commands/export/export_use.py +14 -20
- adam/commands/export/export_x_select.py +48 -0
- adam/commands/export/exporter.py +135 -167
- adam/commands/export/import_files.py +44 -0
- adam/commands/export/import_session.py +11 -35
- adam/commands/export/importer.py +19 -5
- adam/commands/export/importer_athena.py +112 -44
- adam/commands/export/importer_sqlite.py +42 -22
- adam/commands/export/show_column_counts.py +13 -31
- adam/commands/export/show_export_databases.py +7 -7
- adam/commands/export/show_export_session.py +8 -20
- adam/commands/export/show_export_sessions.py +6 -16
- adam/commands/export/utils_export.py +64 -11
- adam/commands/find_files.py +51 -0
- adam/commands/find_processes.py +76 -0
- adam/commands/head.py +36 -0
- adam/commands/help.py +2 -2
- adam/commands/intermediate_command.py +52 -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 +9 -10
- adam/commands/medusa/medusa.py +4 -22
- adam/commands/medusa/medusa_backup.py +20 -27
- adam/commands/medusa/medusa_restore.py +49 -46
- adam/commands/medusa/medusa_show_backupjobs.py +16 -18
- adam/commands/medusa/medusa_show_restorejobs.py +13 -18
- adam/commands/medusa/utils_medusa.py +15 -0
- adam/commands/nodetool.py +7 -21
- adam/commands/param_get.py +11 -14
- adam/commands/param_set.py +8 -12
- adam/commands/postgres/completions_p.py +22 -0
- adam/commands/postgres/postgres.py +34 -57
- adam/commands/postgres/postgres_databases.py +270 -0
- adam/commands/postgres/postgres_ls.py +4 -8
- adam/commands/postgres/postgres_preview.py +5 -9
- adam/commands/postgres/utils_postgres.py +79 -0
- adam/commands/preview_table.py +8 -45
- adam/commands/pwd.py +13 -16
- adam/commands/reaper/reaper.py +4 -27
- adam/commands/reaper/reaper_forward.py +49 -56
- 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 +8 -33
- adam/commands/reaper/reaper_runs.py +43 -58
- adam/commands/reaper/reaper_runs_abort.py +29 -49
- adam/commands/reaper/reaper_schedule_activate.py +14 -33
- adam/commands/reaper/reaper_schedule_start.py +9 -33
- adam/commands/reaper/reaper_schedule_stop.py +9 -33
- adam/commands/reaper/reaper_schedules.py +4 -14
- adam/commands/reaper/reaper_status.py +8 -16
- adam/commands/reaper/utils_reaper.py +203 -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 -40
- adam/commands/repair/repair_stop.py +5 -12
- adam/commands/report.py +27 -29
- adam/commands/restart.py +25 -26
- adam/commands/rollout.py +19 -24
- adam/commands/shell.py +12 -4
- adam/commands/show/show.py +11 -27
- adam/commands/show/show_adam.py +3 -3
- adam/commands/show/show_cassandra_repairs.py +37 -0
- adam/commands/show/show_cassandra_status.py +47 -51
- adam/commands/show/show_cassandra_version.py +5 -18
- adam/commands/show/show_cli_commands.py +56 -0
- adam/commands/show/show_host.py +1 -1
- adam/commands/show/show_login.py +20 -27
- adam/commands/show/show_params.py +2 -5
- adam/commands/show/show_processes.py +18 -21
- adam/commands/show/show_storage.py +11 -20
- adam/commands/watch.py +26 -29
- adam/config.py +5 -16
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/pod_exec_result.py +3 -3
- adam/repl.py +45 -39
- adam/repl_commands.py +26 -19
- adam/repl_session.py +8 -1
- adam/repl_state.py +85 -36
- adam/sql/lark_completer.py +284 -0
- adam/sql/lark_parser.py +604 -0
- adam/sql/sql_completer.py +4 -6
- adam/sql/sql_state_machine.py +29 -16
- 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 +484 -37
- adam/utils_athena.py +19 -19
- adam/utils_audits.py +12 -12
- adam/utils_issues.py +32 -0
- adam/utils_k8s/app_clusters.py +14 -19
- adam/utils_k8s/app_pods.py +7 -2
- adam/utils_k8s/cassandra_clusters.py +30 -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 +96 -0
- adam/utils_k8s/kube_context.py +2 -2
- adam/utils_k8s/pods.py +37 -81
- 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 +6 -14
- adam/utils_local.py +4 -0
- adam/utils_repl/appendable_completer.py +6 -0
- adam/utils_repl/repl_completer.py +128 -2
- adam/utils_repl/state_machine.py +3 -3
- adam/utils_sqlite.py +78 -42
- adam/version.py +1 -1
- {kaqing-2.0.145.dist-info → kaqing-2.0.189.dist-info}/METADATA +1 -1
- kaqing-2.0.189.dist-info/RECORD +253 -0
- kaqing-2.0.189.dist-info/top_level.txt +2 -0
- teddy/__init__.py +0 -0
- teddy/lark_parser.py +436 -0
- teddy/lark_parser2.py +618 -0
- adam/commands/app.py +0 -67
- adam/commands/cp.py +0 -95
- adam/commands/cql/cql_completions.py +0 -28
- adam/commands/export/clean_up_export_session.py +0 -53
- adam/commands/export/export_select_x.py +0 -54
- adam/commands/postgres/postgres_context.py +0 -248
- adam/commands/postgres/postgres_utils.py +0 -31
- adam/commands/postgres/psql_completions.py +0 -10
- adam/commands/reaper/reaper_session.py +0 -159
- adam/commands/show/show_app_actions.py +0 -56
- adam/commands/show/show_commands.py +0 -61
- adam/commands/show/show_repairs.py +0 -47
- kaqing-2.0.145.dist-info/RECORD +0 -227
- kaqing-2.0.145.dist-info/top_level.txt +0 -1
- {kaqing-2.0.145.dist-info → kaqing-2.0.189.dist-info}/WHEEL +0 -0
- {kaqing-2.0.145.dist-info → kaqing-2.0.189.dist-info}/entry_points.txt +0 -0
adam/utils_k8s/pods.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
3
2
|
from datetime import datetime
|
|
3
|
+
import os
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
-
from typing import TypeVar
|
|
6
|
+
from typing import TypeVar
|
|
7
7
|
from kubernetes import client
|
|
8
8
|
from kubernetes.stream import stream
|
|
9
9
|
from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
|
|
@@ -11,7 +11,8 @@ from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
|
|
|
11
11
|
from adam.config import Config
|
|
12
12
|
from adam.utils_k8s.volumes import ConfigMapMount
|
|
13
13
|
from adam.pod_exec_result import PodExecResult
|
|
14
|
-
from adam.utils import
|
|
14
|
+
from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_exc
|
|
15
|
+
from adam.utils_local import local_tmp_dir
|
|
15
16
|
from .kube_context import KubeContext
|
|
16
17
|
|
|
17
18
|
from websocket._core import WebSocket
|
|
@@ -30,11 +31,9 @@ class Pods:
|
|
|
30
31
|
return _TEST_POD_EXEC_OUTS
|
|
31
32
|
|
|
32
33
|
def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
|
|
33
|
-
|
|
34
|
+
with log_exc(lambda e: "Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e):
|
|
34
35
|
v1 = client.CoreV1Api()
|
|
35
36
|
v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
|
|
36
|
-
except Exception as e:
|
|
37
|
-
log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
|
|
38
37
|
|
|
39
38
|
def delete_with_selector(namespace: str, label_selector: str, grace_period_seconds: int = None):
|
|
40
39
|
v1 = client.CoreV1Api()
|
|
@@ -43,68 +42,22 @@ class Pods:
|
|
|
43
42
|
for i in ret.items:
|
|
44
43
|
v1.delete_namespaced_pod(name=i.metadata.name, namespace=namespace, grace_period_seconds=grace_period_seconds)
|
|
45
44
|
|
|
46
|
-
def
|
|
47
|
-
namespace: str,
|
|
48
|
-
body: Callable[[ThreadPoolExecutor, str, str, bool], T],
|
|
49
|
-
post: Callable[[T], T] = None,
|
|
50
|
-
action: str = 'action',
|
|
51
|
-
max_workers=0,
|
|
52
|
-
show_out=True,
|
|
53
|
-
on_any = False,
|
|
54
|
-
background = False) -> list[T]:
|
|
55
|
-
show_out = KubeContext.show_out(show_out)
|
|
56
|
-
|
|
45
|
+
def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, action: str = 'action'):
|
|
57
46
|
if not max_workers:
|
|
58
47
|
max_workers = Config().action_workers(action, 0)
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if post:
|
|
73
|
-
rs = [post(r, show_out=show_out) for r in rs]
|
|
74
|
-
|
|
75
|
-
return rs
|
|
76
|
-
finally:
|
|
77
|
-
if KubeContext.show_parallelism():
|
|
78
|
-
log2(f"Parallel {action} elapsed time: {elapsed_time(start_time)} with {max_workers} workers")
|
|
79
|
-
else:
|
|
80
|
-
results: list[T] = []
|
|
81
|
-
|
|
82
|
-
samples = 1 if on_any else Config().action_node_samples(action, sys.maxsize)
|
|
83
|
-
l = min(len(pods), samples)
|
|
84
|
-
adj = 'all'
|
|
85
|
-
if l < len(pods):
|
|
86
|
-
adj = f'{l} sample'
|
|
87
|
-
if show_out:
|
|
88
|
-
log2(f'Executing on {adj} nodes from statefulset...')
|
|
89
|
-
for pod_name in pods:
|
|
90
|
-
try:
|
|
91
|
-
# disable stdout from the pod_exec, then show the output in a for loop
|
|
92
|
-
result = body(None, pod_name, namespace, False)
|
|
93
|
-
if post:
|
|
94
|
-
result = post(result, show_out=show_out)
|
|
95
|
-
results.append(result)
|
|
96
|
-
if result:
|
|
97
|
-
l -= 1
|
|
98
|
-
if not l:
|
|
99
|
-
break
|
|
100
|
-
except Exception as e:
|
|
101
|
-
log2(e)
|
|
102
|
-
|
|
103
|
-
return results
|
|
104
|
-
|
|
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,
|
|
48
|
+
if samples == sys.maxsize:
|
|
49
|
+
samples = Config().action_node_samples(action, sys.maxsize)
|
|
50
|
+
|
|
51
|
+
return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
|
|
52
|
+
|
|
53
|
+
def exec(pod_name: str,
|
|
54
|
+
container: str,
|
|
55
|
+
namespace: str,
|
|
56
|
+
command: str,
|
|
57
|
+
show_out = True,
|
|
58
|
+
throw_err = False,
|
|
59
|
+
shell = '/bin/sh',
|
|
60
|
+
backgrounded = False,
|
|
108
61
|
log_file = None,
|
|
109
62
|
interaction: Callable[[any, list[str]], any] = None,
|
|
110
63
|
env_prefix: str = None):
|
|
@@ -120,7 +73,7 @@ class Pods:
|
|
|
120
73
|
if env_prefix:
|
|
121
74
|
exec_command = [shell, '-c', f'{env_prefix} {command}']
|
|
122
75
|
|
|
123
|
-
if
|
|
76
|
+
if backgrounded or command.endswith(' &'):
|
|
124
77
|
# should be false for starting a background process
|
|
125
78
|
tty = False
|
|
126
79
|
|
|
@@ -138,8 +91,7 @@ class Pods:
|
|
|
138
91
|
exec_command = [shell, '-c', command]
|
|
139
92
|
|
|
140
93
|
k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
|
|
141
|
-
|
|
142
|
-
print(k_command)
|
|
94
|
+
debug(k_command)
|
|
143
95
|
|
|
144
96
|
resp: WSClient = stream(
|
|
145
97
|
api.connect_get_namespaced_pod_exec,
|
|
@@ -173,11 +125,9 @@ class Pods:
|
|
|
173
125
|
stderr.append(frag)
|
|
174
126
|
if show_out: print(frag, end="")
|
|
175
127
|
|
|
176
|
-
|
|
128
|
+
with log_exc():
|
|
177
129
|
# get the exit code from server
|
|
178
130
|
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
179
|
-
except Exception as e:
|
|
180
|
-
pass
|
|
181
131
|
except Exception as e:
|
|
182
132
|
if throw_err:
|
|
183
133
|
raise e
|
|
@@ -186,10 +136,8 @@ class Pods:
|
|
|
186
136
|
finally:
|
|
187
137
|
resp.close()
|
|
188
138
|
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
189
|
-
|
|
139
|
+
with log_exc():
|
|
190
140
|
s.sock.close()
|
|
191
|
-
except:
|
|
192
|
-
pass
|
|
193
141
|
|
|
194
142
|
return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
|
|
195
143
|
|
|
@@ -214,20 +162,28 @@ class Pods:
|
|
|
214
162
|
if resp.peek_stdout():
|
|
215
163
|
yield resp.read_stdout()
|
|
216
164
|
|
|
217
|
-
|
|
165
|
+
with log_exc():
|
|
218
166
|
# get the exit code from server
|
|
219
167
|
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
220
|
-
except Exception as e:
|
|
221
|
-
pass
|
|
222
168
|
except Exception as e:
|
|
223
169
|
raise e
|
|
224
170
|
finally:
|
|
225
171
|
resp.close()
|
|
226
172
|
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
227
|
-
|
|
173
|
+
with log_exc():
|
|
228
174
|
s.sock.close()
|
|
229
|
-
|
|
230
|
-
|
|
175
|
+
|
|
176
|
+
def download_file(pod_name: str, container: str, namespace: str, from_path: str, to_path: str = None):
|
|
177
|
+
if not to_path:
|
|
178
|
+
to_path = f'{local_tmp_dir()}/{os.path.basename(from_path)}'
|
|
179
|
+
|
|
180
|
+
bytes = Pods.read_file(pod_name, container, namespace, from_path)
|
|
181
|
+
with open(to_path, 'wb') as f:
|
|
182
|
+
for item in GeneratorStream(bytes):
|
|
183
|
+
f.write(item)
|
|
184
|
+
|
|
185
|
+
return to_path
|
|
186
|
+
|
|
231
187
|
def get_container(namespace: str, pod_name: str, container_name: str):
|
|
232
188
|
pod = Pods.get(namespace, pod_name)
|
|
233
189
|
if not pod:
|
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,18 +55,12 @@ 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
|
-
def pod_names(
|
|
72
|
-
|
|
59
|
+
def pod_names(sts: str, ns: str):
|
|
60
|
+
if not sts:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
return [pod.metadata.name for pod in StatefulSets.pods(sts, ns)]
|
|
73
64
|
|
|
74
65
|
def restarted_at(ss: str, ns: str):
|
|
75
66
|
# returns timestamp and if being rolled out
|
|
@@ -92,6 +83,7 @@ class StatefulSets:
|
|
|
92
83
|
|
|
93
84
|
return restarted, False
|
|
94
85
|
|
|
86
|
+
@functools.lru_cache()
|
|
95
87
|
def get_datacenter(sts: str, ns: str) -> str:
|
|
96
88
|
v1 = client.AppsV1Api()
|
|
97
89
|
namespace = ns
|
adam/utils_local.py
ADDED
|
@@ -1,14 +1,58 @@
|
|
|
1
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
2
|
+
import copy
|
|
3
|
+
import inspect
|
|
1
4
|
import re
|
|
2
|
-
|
|
3
|
-
|
|
5
|
+
import threading
|
|
6
|
+
import traceback
|
|
7
|
+
from typing import Iterable, TypeVar, cast
|
|
8
|
+
from prompt_toolkit.completion import CompleteEvent, Completer, Completion, NestedCompleter, WordCompleter
|
|
4
9
|
from prompt_toolkit.document import Document
|
|
5
10
|
|
|
11
|
+
from adam.utils import debug, debug_complete, log2, log_timing
|
|
12
|
+
from adam.utils_repl.appendable_completer import AppendableCompleter
|
|
13
|
+
|
|
14
|
+
import nest_asyncio
|
|
15
|
+
nest_asyncio.apply()
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
|
|
6
19
|
__all__ = [
|
|
7
20
|
"ReplCompleter",
|
|
8
21
|
]
|
|
9
22
|
|
|
10
23
|
T = TypeVar('T')
|
|
11
24
|
|
|
25
|
+
def merge_completions(dict1, dict2):
|
|
26
|
+
if isinstance(dict1, dict):
|
|
27
|
+
target = dict1.copy()
|
|
28
|
+
else:
|
|
29
|
+
target = copy.copy(dict1)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
for key, value in dict2.items():
|
|
33
|
+
if key in target:
|
|
34
|
+
debug_complete(f'[{key}] {type(dict2)} is being merged to {type(target[key])} completions')
|
|
35
|
+
if isinstance(value, dict):
|
|
36
|
+
if isinstance(target[key], dict):
|
|
37
|
+
target[key] = merge_completions(target[key], value)
|
|
38
|
+
elif isinstance(target[key], AppendableCompleter):
|
|
39
|
+
cast(AppendableCompleter, target[key]).append_completions(key, value)
|
|
40
|
+
elif isinstance(value, AppendableCompleter):
|
|
41
|
+
if isinstance(target[key], dict):
|
|
42
|
+
cast(AppendableCompleter, value).append_completions(key, target[key])
|
|
43
|
+
target[key] = value
|
|
44
|
+
else:
|
|
45
|
+
log2(f'* {key} of {type(value)} is overriding existing {type(target[key])} completions')
|
|
46
|
+
else:
|
|
47
|
+
target[key] = value
|
|
48
|
+
else:
|
|
49
|
+
target[key] = value
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
traceback.print_exc()
|
|
53
|
+
|
|
54
|
+
return target
|
|
55
|
+
|
|
12
56
|
class ReplCompleter(NestedCompleter):
|
|
13
57
|
def get_completions(
|
|
14
58
|
self, document: Document, complete_event: CompleteEvent
|
|
@@ -42,5 +86,87 @@ class ReplCompleter(NestedCompleter):
|
|
|
42
86
|
# Allow dot in the middle or a word
|
|
43
87
|
list(self.options.keys()), ignore_case=self.ignore_case, pattern=re.compile(r"([a-zA-Z0-9_\.\@\&]+|[^a-zA-Z0-9_\.\@\&\s]+)")
|
|
44
88
|
)
|
|
89
|
+
for c in completer.get_completions(document, complete_event):
|
|
90
|
+
yield c
|
|
91
|
+
|
|
92
|
+
lock = threading.Lock()
|
|
93
|
+
in_queue = set()
|
|
94
|
+
|
|
95
|
+
def preload(action: callable, log_key: str = None):
|
|
96
|
+
with lock:
|
|
97
|
+
if not LazyNestedCompleter.loop:
|
|
98
|
+
LazyNestedCompleter.loop = asyncio.new_event_loop()
|
|
99
|
+
LazyNestedCompleter.async_exec = ThreadPoolExecutor(max_workers=6, thread_name_prefix='async')
|
|
100
|
+
LazyNestedCompleter.loop.set_default_executor(LazyNestedCompleter.async_exec)
|
|
101
|
+
|
|
102
|
+
# some lib does not handle asyncio loop properly, as sync exec submit does not work, use another async loop
|
|
103
|
+
async def a():
|
|
104
|
+
try:
|
|
105
|
+
arg_needed = len(action.__code__.co_varnames)
|
|
106
|
+
|
|
107
|
+
if log_key:
|
|
108
|
+
with log_timing(log_key):
|
|
109
|
+
r = action(None) if arg_needed else action()
|
|
110
|
+
else:
|
|
111
|
+
r = action(None) if arg_needed else action()
|
|
112
|
+
if inspect.isawaitable(r):
|
|
113
|
+
await r
|
|
114
|
+
|
|
115
|
+
in_queue.remove(log_key)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
log2('preloading error', e, inspect.getsourcelines(action)[0][0])
|
|
118
|
+
traceback.print_exc()
|
|
119
|
+
|
|
120
|
+
if log_key not in in_queue:
|
|
121
|
+
in_queue.add(log_key)
|
|
122
|
+
LazyNestedCompleter.async_exec.submit(lambda: LazyNestedCompleter.loop.run_until_complete(a()))
|
|
123
|
+
|
|
124
|
+
class LazyNestedCompleter(NestedCompleter):
|
|
125
|
+
loop: asyncio.AbstractEventLoop = None
|
|
126
|
+
async_exec: ThreadPoolExecutor = None
|
|
127
|
+
|
|
128
|
+
def __init__(self, name: str, options_lambda: callable, ignore_case: bool = True, auto: str = 'lazy') -> None:
|
|
129
|
+
super().__init__(None, ignore_case)
|
|
130
|
+
self.options_lambda = options_lambda
|
|
131
|
+
if auto == 'lazy':
|
|
132
|
+
preload(options_lambda, log_key=name)
|
|
133
|
+
|
|
134
|
+
def __repr__(self) -> str:
|
|
135
|
+
return "LazyNestedCompleter(%r, ignore_case=%r)" % (self.options, self.ignore_case)
|
|
136
|
+
|
|
137
|
+
def get_completions(
|
|
138
|
+
self, document: Document, complete_event: CompleteEvent
|
|
139
|
+
) -> Iterable[Completion]:
|
|
140
|
+
if not self.options:
|
|
141
|
+
self.options = self.options_lambda()
|
|
142
|
+
|
|
143
|
+
# Split document.
|
|
144
|
+
text = document.text_before_cursor.lstrip()
|
|
145
|
+
stripped_len = len(document.text_before_cursor) - len(text)
|
|
146
|
+
|
|
147
|
+
# If there is a space, check for the first term, and use a
|
|
148
|
+
# subcompleter.
|
|
149
|
+
if " " in text:
|
|
150
|
+
first_term = text.split()[0]
|
|
151
|
+
completer = self.options.get(first_term)
|
|
152
|
+
|
|
153
|
+
# If we have a sub completer, use this for the completions.
|
|
154
|
+
if completer is not None:
|
|
155
|
+
remaining_text = text[len(first_term) :].lstrip()
|
|
156
|
+
move_cursor = len(text) - len(remaining_text) + stripped_len
|
|
157
|
+
|
|
158
|
+
new_document = Document(
|
|
159
|
+
remaining_text,
|
|
160
|
+
cursor_position=document.cursor_position - move_cursor,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
for c in completer.get_completions(new_document, complete_event):
|
|
164
|
+
yield c
|
|
165
|
+
|
|
166
|
+
# No space in the input: behave exactly like `WordCompleter`.
|
|
167
|
+
else:
|
|
168
|
+
completer = WordCompleter(
|
|
169
|
+
list(self.options.keys()), ignore_case=self.ignore_case
|
|
170
|
+
)
|
|
45
171
|
for c in completer.get_completions(document, complete_event):
|
|
46
172
|
yield c
|
adam/utils_repl/state_machine.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
from typing import Generic, TypeVar
|
|
3
3
|
|
|
4
|
+
from adam.utils import log_exc
|
|
5
|
+
|
|
4
6
|
__all__ = [
|
|
5
7
|
'State',
|
|
6
8
|
'StateMachine',
|
|
@@ -160,14 +162,12 @@ class StateMachine(Generic[T]):
|
|
|
160
162
|
last_name = token
|
|
161
163
|
token = 'word'
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
with log_exc():
|
|
164
166
|
context = state.context
|
|
165
167
|
state = self.states[f'{state.state} > {token}']
|
|
166
168
|
state.context = context
|
|
167
169
|
|
|
168
170
|
if last_name:
|
|
169
171
|
state.context['last_name'] = last_name
|
|
170
|
-
except:
|
|
171
|
-
pass
|
|
172
172
|
|
|
173
173
|
return state
|
adam/utils_sqlite.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
1
3
|
import functools
|
|
2
4
|
import glob
|
|
3
5
|
import os
|
|
@@ -5,41 +7,60 @@ import sqlite3
|
|
|
5
7
|
import pandas
|
|
6
8
|
|
|
7
9
|
from adam.config import Config
|
|
8
|
-
from adam.utils import
|
|
10
|
+
from adam.utils import tabulize, log, wait_log
|
|
11
|
+
|
|
12
|
+
class CursorHandler:
|
|
13
|
+
def __init__(self, conn: sqlite3.Connection):
|
|
14
|
+
self.conn = conn
|
|
15
|
+
self.cursor = None
|
|
16
|
+
|
|
17
|
+
def __enter__(self):
|
|
18
|
+
self.cursor = self.conn.cursor()
|
|
19
|
+
|
|
20
|
+
return self.cursor
|
|
21
|
+
|
|
22
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
23
|
+
if self.cursor:
|
|
24
|
+
self.cursor.close()
|
|
25
|
+
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
class SQLiteConnectionHandler:
|
|
29
|
+
def __init__(self, database: str, keyspace = None):
|
|
30
|
+
self.database = database
|
|
31
|
+
self.keyspace = keyspace
|
|
32
|
+
self.conn = None
|
|
33
|
+
|
|
34
|
+
def __enter__(self) -> sqlite3.Connection:
|
|
35
|
+
self.conn = SQLite.connect(self.database, self.keyspace)
|
|
36
|
+
|
|
37
|
+
self.conn.__enter__()
|
|
38
|
+
|
|
39
|
+
return self.conn
|
|
40
|
+
|
|
41
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
42
|
+
if self.conn:
|
|
43
|
+
self.conn.__exit__(exc_type, exc_val, exc_tb)
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def sqlite(database: str, keyspace = None):
|
|
48
|
+
return SQLiteConnectionHandler(database, keyspace=keyspace)
|
|
9
49
|
|
|
10
50
|
# no state utility class
|
|
11
51
|
class SQLite:
|
|
52
|
+
def cursor(conn: sqlite3.Connection):
|
|
53
|
+
return CursorHandler(conn)
|
|
54
|
+
|
|
12
55
|
def local_db_dir():
|
|
13
56
|
return Config().get('export.sqlite.local-db-dir', '/tmp/qing-db')
|
|
14
57
|
|
|
15
58
|
def keyspace(database: str):
|
|
16
59
|
return '_'.join(database.replace(".db", "").split('_')[1:])
|
|
17
60
|
|
|
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
61
|
@functools.lru_cache()
|
|
41
62
|
def database_names(prefix: str = None):
|
|
42
|
-
|
|
63
|
+
wait_log('Inspecting export databases...')
|
|
43
64
|
|
|
44
65
|
pattern = f'{SQLite.local_db_dir()}/s*.db'
|
|
45
66
|
if prefix:
|
|
@@ -60,15 +81,10 @@ class SQLite:
|
|
|
60
81
|
tables = []
|
|
61
82
|
try:
|
|
62
83
|
conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{ts_prefix}_{keyspace}.db')
|
|
63
|
-
cursor
|
|
64
|
-
try:
|
|
65
|
-
cursor = conn.cursor()
|
|
84
|
+
with SQLite.cursor(conn) as cursor:
|
|
66
85
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
|
|
67
86
|
|
|
68
87
|
tables = [row[0] for row in cursor.fetchall() if row[0] != "sqlite_sequence"]
|
|
69
|
-
finally:
|
|
70
|
-
if cursor:
|
|
71
|
-
cursor.close()
|
|
72
88
|
|
|
73
89
|
return tables
|
|
74
90
|
except sqlite3.Error as e:
|
|
@@ -78,24 +94,44 @@ class SQLite:
|
|
|
78
94
|
if conn:
|
|
79
95
|
conn.close()
|
|
80
96
|
|
|
97
|
+
def connect(database: str, keyspace: str = None):
|
|
98
|
+
os.makedirs(SQLite.local_db_dir(), exist_ok=True)
|
|
99
|
+
|
|
100
|
+
if keyspace:
|
|
101
|
+
return sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_{keyspace}.db')
|
|
102
|
+
else:
|
|
103
|
+
conn = sqlite3.connect(f'{SQLite.local_db_dir()}/{database}_root.db')
|
|
104
|
+
with SQLite.cursor(conn) as cursor:
|
|
105
|
+
for d in SQLite.database_names(database):
|
|
106
|
+
if d != f'{database}.db':
|
|
107
|
+
q = f"ATTACH DATABASE '{SQLite.local_db_dir()}/{d}' AS {SQLite.keyspace(d)}"
|
|
108
|
+
cursor.execute(q)
|
|
109
|
+
|
|
110
|
+
return conn
|
|
111
|
+
|
|
81
112
|
@functools.lru_cache()
|
|
82
113
|
def column_names(tables: list[str] = [], database: str = None, function: str = 'audit', partition_cols_only = False):
|
|
83
114
|
pass
|
|
84
115
|
|
|
85
|
-
def run_query(query: str, database: str = None,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
def run_query(query: str, database: str = None, output: Callable[[str], str] = None) -> tuple[int, str]:
|
|
117
|
+
with sqlite(database) as conn:
|
|
118
|
+
return SQLite.run_query_with_conn(conn, query, output=output)
|
|
119
|
+
|
|
120
|
+
def run_query_with_conn(conn, query: str, output: Callable[[str], str] = None) -> tuple[int, str]:
|
|
121
|
+
log_file = None
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
123
|
+
df = SQLite.query(conn, query)
|
|
124
|
+
lines = ['\t'.join(map(str, line)) for line in df.values.tolist()]
|
|
125
|
+
out = tabulize(lines, header='\t'.join(df.columns.tolist()), separator='\t', to=0)
|
|
126
|
+
if output:
|
|
127
|
+
log_file = output(out)
|
|
128
|
+
else:
|
|
129
|
+
log(out)
|
|
94
130
|
|
|
95
|
-
|
|
96
|
-
finally:
|
|
97
|
-
if conn:
|
|
98
|
-
conn.close()
|
|
131
|
+
return len(lines), log_file
|
|
99
132
|
|
|
100
133
|
def query(conn, sql: str) -> tuple[str, str, list]:
|
|
101
134
|
return pandas.read_sql_query(sql, conn)
|
|
135
|
+
|
|
136
|
+
def log_prefix():
|
|
137
|
+
return Config().get('export.log-prefix', '/tmp/qing')
|