kaqing 2.0.98__py3-none-any.whl → 2.0.171__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adam/__init__.py +0 -2
- adam/app_session.py +9 -7
- adam/batch.py +4 -18
- adam/checks/check_utils.py +14 -46
- adam/checks/cpu.py +7 -1
- adam/checks/cpu_metrics.py +52 -0
- adam/columns/columns.py +3 -1
- adam/columns/cpu.py +3 -1
- adam/columns/cpu_metrics.py +22 -0
- adam/commands/__init__.py +15 -0
- adam/commands/alter_tables.py +50 -61
- adam/commands/app_cmd.py +38 -0
- adam/commands/app_ping.py +8 -14
- adam/commands/audit/audit.py +43 -30
- adam/commands/audit/audit_repair_tables.py +26 -46
- adam/commands/audit/audit_run.py +50 -0
- adam/commands/audit/show_last10.py +48 -0
- adam/commands/audit/show_slow10.py +47 -0
- adam/commands/audit/show_top10.py +45 -0
- adam/commands/audit/utils_show_top10.py +59 -0
- adam/commands/bash/__init__.py +5 -0
- adam/commands/bash/bash.py +36 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/bash/utils_bash.py +16 -0
- adam/commands/cat.py +50 -0
- adam/commands/cd.py +15 -91
- adam/commands/check.py +23 -18
- adam/commands/cli_commands.py +2 -3
- adam/commands/code.py +57 -0
- adam/commands/command.py +96 -40
- adam/commands/commands_utils.py +9 -19
- adam/commands/cp.py +33 -39
- adam/commands/cql/cql_completions.py +30 -8
- adam/commands/cql/cqlsh.py +12 -27
- 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 -21
- adam/commands/deploy/deploy_frontend.py +14 -17
- adam/commands/deploy/deploy_pg_agent.py +3 -6
- adam/commands/deploy/deploy_pod.py +67 -73
- adam/commands/deploy/deploy_utils.py +14 -24
- adam/commands/deploy/undeploy.py +4 -21
- adam/commands/deploy/undeploy_frontend.py +4 -7
- adam/commands/deploy/undeploy_pg_agent.py +6 -8
- adam/commands/deploy/undeploy_pod.py +11 -12
- 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/__init__.py +0 -0
- 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 +53 -0
- adam/commands/export/export_databases.py +170 -0
- adam/commands/export/export_handlers.py +71 -0
- adam/commands/export/export_select.py +81 -0
- adam/commands/export/export_select_x.py +54 -0
- adam/commands/export/export_use.py +52 -0
- adam/commands/export/exporter.py +352 -0
- adam/commands/export/import_session.py +40 -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 +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 +314 -0
- adam/commands/help.py +10 -6
- adam/commands/intermediate_command.py +49 -0
- adam/commands/issues.py +14 -40
- adam/commands/kubectl.py +38 -0
- adam/commands/login.py +28 -24
- adam/commands/logs.py +4 -6
- adam/commands/ls.py +11 -116
- adam/commands/medusa/medusa.py +4 -22
- adam/commands/medusa/medusa_backup.py +20 -24
- adam/commands/medusa/medusa_restore.py +30 -32
- adam/commands/medusa/medusa_show_backupjobs.py +16 -17
- adam/commands/medusa/medusa_show_restorejobs.py +12 -17
- adam/commands/nodetool.py +11 -17
- adam/commands/param_get.py +11 -12
- adam/commands/param_set.py +9 -10
- adam/commands/postgres/postgres.py +43 -36
- adam/commands/postgres/{postgres_session.py → postgres_context.py} +80 -46
- adam/commands/postgres/postgres_ls.py +4 -8
- adam/commands/postgres/postgres_preview.py +5 -9
- adam/commands/postgres/psql_completions.py +2 -2
- adam/commands/postgres/utils_postgres.py +66 -0
- adam/commands/preview_table.py +8 -61
- adam/commands/pwd.py +14 -44
- adam/commands/reaper/reaper.py +4 -24
- 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 +4 -7
- adam/commands/repair/repair_run.py +27 -29
- adam/commands/repair/repair_scan.py +31 -34
- adam/commands/repair/repair_stop.py +4 -7
- adam/commands/report.py +25 -21
- adam/commands/restart.py +25 -26
- adam/commands/rollout.py +19 -24
- adam/commands/shell.py +5 -4
- adam/commands/show/show.py +6 -19
- adam/commands/show/show_app_actions.py +26 -22
- adam/commands/show/show_app_id.py +8 -11
- adam/commands/show/show_app_queues.py +7 -10
- adam/commands/show/{show_repairs.py → show_cassandra_repairs.py} +8 -17
- adam/commands/show/show_cassandra_status.py +29 -33
- adam/commands/show/show_cassandra_version.py +4 -14
- adam/commands/show/show_commands.py +19 -21
- adam/commands/show/show_host.py +1 -1
- adam/commands/show/show_login.py +26 -24
- adam/commands/show/show_processes.py +16 -18
- 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/pod_exec_result.py +7 -1
- adam/repl.py +95 -131
- adam/repl_commands.py +48 -20
- adam/repl_state.py +270 -61
- adam/sql/sql_completer.py +105 -63
- adam/sql/sql_state_machine.py +618 -0
- adam/sql/term_completer.py +3 -0
- adam/sso/authn_ad.py +6 -5
- adam/sso/authn_okta.py +3 -3
- adam/sso/cred_cache.py +3 -2
- adam/sso/idp.py +3 -3
- adam/utils.py +439 -3
- adam/utils_app.py +98 -0
- adam/utils_athena.py +140 -87
- adam/utils_audits.py +106 -0
- adam/utils_issues.py +32 -0
- adam/utils_k8s/app_clusters.py +28 -0
- adam/utils_k8s/app_pods.py +33 -0
- adam/utils_k8s/cassandra_clusters.py +22 -20
- adam/utils_k8s/cassandra_nodes.py +4 -4
- adam/utils_k8s/custom_resources.py +5 -0
- adam/utils_k8s/ingresses.py +2 -2
- adam/utils_k8s/k8s.py +87 -0
- adam/utils_k8s/pods.py +77 -68
- 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 +109 -0
- adam/version.py +1 -1
- {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/METADATA +1 -1
- kaqing-2.0.171.dist-info/RECORD +236 -0
- adam/commands/app.py +0 -67
- adam/commands/bash.py +0 -92
- adam/commands/cql/cql_table_completer.py +0 -8
- adam/commands/cql/cql_utils.py +0 -115
- adam/commands/describe/describe.py +0 -47
- adam/commands/describe/describe_keyspace.py +0 -60
- adam/commands/describe/describe_keyspaces.py +0 -49
- adam/commands/describe/describe_schema.py +0 -49
- adam/commands/describe/describe_table.py +0 -60
- adam/commands/describe/describe_tables.py +0 -49
- adam/commands/devices.py +0 -118
- adam/commands/postgres/postgres_utils.py +0 -31
- adam/commands/postgres/psql_table_completer.py +0 -11
- adam/commands/reaper/reaper_session.py +0 -159
- adam/sql/state_machine.py +0 -460
- kaqing-2.0.98.dist-info/RECORD +0 -191
- /adam/commands/{describe → devices}/__init__.py +0 -0
- {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/WHEEL +0 -0
- {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/entry_points.txt +0 -0
- {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/top_level.txt +0 -0
adam/utils_k8s/k8s.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
import re
|
|
3
|
+
import portforward
|
|
4
|
+
|
|
5
|
+
from adam.commands.command import InvalidState
|
|
6
|
+
from adam.repl_state import ReplState
|
|
7
|
+
from adam.utils import log2
|
|
8
|
+
from adam.utils_k8s.kube_context import KubeContext
|
|
9
|
+
|
|
10
|
+
class PortForwardHandler:
|
|
11
|
+
connections: dict[str, int] = {}
|
|
12
|
+
|
|
13
|
+
def __init__(self, state: ReplState, local_port: int, svc_or_pod: Callable[[bool],str], target_port: int):
|
|
14
|
+
self.state = state
|
|
15
|
+
self.local_port = local_port
|
|
16
|
+
self.svc_or_pod = svc_or_pod
|
|
17
|
+
self.target_port = target_port
|
|
18
|
+
self.forward_connection = None
|
|
19
|
+
self.pod = None
|
|
20
|
+
|
|
21
|
+
def __enter__(self) -> tuple[str, str]:
|
|
22
|
+
state = self.state
|
|
23
|
+
|
|
24
|
+
if not self.svc_or_pod:
|
|
25
|
+
log2('No service or pod found.')
|
|
26
|
+
|
|
27
|
+
raise InvalidState(state)
|
|
28
|
+
|
|
29
|
+
if KubeContext.in_cluster():
|
|
30
|
+
svc_name = self.svc_or_pod(True)
|
|
31
|
+
if not svc_name:
|
|
32
|
+
log2('No service found.')
|
|
33
|
+
|
|
34
|
+
raise InvalidState(state)
|
|
35
|
+
|
|
36
|
+
# cs-a526330d23-cs-a526330d23-default-sts-0 ->
|
|
37
|
+
# curl http://cs-a526330d23-cs-a526330d23-reaper-service.stgawsscpsr.svc.cluster.local:8080
|
|
38
|
+
groups = re.match(r'^(.*?-.*?-.*?-.*?-).*', state.sts)
|
|
39
|
+
if groups:
|
|
40
|
+
svc = f'{groups[1]}{svc_name}.{state.namespace}.svc.cluster.local:{self.target_port}'
|
|
41
|
+
return (svc, svc)
|
|
42
|
+
else:
|
|
43
|
+
raise InvalidState(state)
|
|
44
|
+
else:
|
|
45
|
+
pod = self.svc_or_pod(False)
|
|
46
|
+
if not pod:
|
|
47
|
+
log2('No pod found.')
|
|
48
|
+
|
|
49
|
+
raise InvalidState(state)
|
|
50
|
+
|
|
51
|
+
self.pod = pod
|
|
52
|
+
self.forward_connection = portforward.forward(state.namespace, pod, self.local_port, self.target_port)
|
|
53
|
+
if self.inc_connection_cnt() == 1:
|
|
54
|
+
self.forward_connection.__enter__()
|
|
55
|
+
|
|
56
|
+
return (f'localhost:{self.local_port}', f'{pod}:{self.target_port}')
|
|
57
|
+
|
|
58
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
59
|
+
if self.forward_connection:
|
|
60
|
+
if not self.dec_connection_cnt():
|
|
61
|
+
return self.forward_connection.__exit__(exc_type, exc_val, exc_tb)
|
|
62
|
+
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def inc_connection_cnt(self):
|
|
66
|
+
id = self.connection_id(self.pod)
|
|
67
|
+
if id not in PortForwardHandler.connections:
|
|
68
|
+
PortForwardHandler.connections[id] = 1
|
|
69
|
+
else:
|
|
70
|
+
PortForwardHandler.connections[id] += 1
|
|
71
|
+
|
|
72
|
+
return PortForwardHandler.connections[id]
|
|
73
|
+
|
|
74
|
+
def dec_connection_cnt(self):
|
|
75
|
+
id = self.connection_id(self.pod)
|
|
76
|
+
if id not in PortForwardHandler.connections:
|
|
77
|
+
PortForwardHandler.connections[id] = 0
|
|
78
|
+
elif PortForwardHandler.connections[id] > 0:
|
|
79
|
+
PortForwardHandler.connections[id] -= 1
|
|
80
|
+
|
|
81
|
+
return PortForwardHandler.connections[id]
|
|
82
|
+
|
|
83
|
+
def connection_id(self, pod: str):
|
|
84
|
+
return f'{self.local_port}:{pod}:{self.target_port}'
|
|
85
|
+
|
|
86
|
+
def port_forwarding(state: ReplState, local_port: int, svc_or_pod: Callable[[bool],str], target_port: int):
|
|
87
|
+
return PortForwardHandler(state, local_port, svc_or_pod, target_port)
|
adam/utils_k8s/pods.py
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
import sys
|
|
5
4
|
import time
|
|
6
|
-
from typing import TypeVar
|
|
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
|
|
15
14
|
from .kube_context import KubeContext
|
|
16
15
|
|
|
16
|
+
from websocket._core import WebSocket
|
|
17
|
+
|
|
17
18
|
T = TypeVar('T')
|
|
18
19
|
_TEST_POD_EXEC_OUTS: PodExecResult = None
|
|
19
20
|
|
|
20
21
|
# utility collection on pods; methods are all static
|
|
21
22
|
class Pods:
|
|
23
|
+
_TEST_POD_CLOSE_SOCKET: bool = False
|
|
24
|
+
|
|
22
25
|
def set_test_pod_exec_outs(outs: PodExecResult):
|
|
23
26
|
global _TEST_POD_EXEC_OUTS
|
|
24
27
|
_TEST_POD_EXEC_OUTS = outs
|
|
@@ -28,7 +31,7 @@ class Pods:
|
|
|
28
31
|
def delete(pod_name: str, namespace: str, grace_period_seconds: int = None):
|
|
29
32
|
try:
|
|
30
33
|
v1 = client.CoreV1Api()
|
|
31
|
-
|
|
34
|
+
v1.delete_namespaced_pod(pod_name, namespace, grace_period_seconds=grace_period_seconds)
|
|
32
35
|
except Exception as e:
|
|
33
36
|
log2("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
|
|
34
37
|
|
|
@@ -39,63 +42,20 @@ class Pods:
|
|
|
39
42
|
for i in ret.items:
|
|
40
43
|
v1.delete_namespaced_pod(name=i.metadata.name, namespace=namespace, grace_period_seconds=grace_period_seconds)
|
|
41
44
|
|
|
42
|
-
def
|
|
43
|
-
namespace: str,
|
|
44
|
-
body: Callable[[ThreadPoolExecutor, str, str, bool], T],
|
|
45
|
-
post: Callable[[T], T] = None,
|
|
46
|
-
action: str = 'action', max_workers=0, show_out=True, on_any = False) -> list[T]:
|
|
47
|
-
show_out = KubeContext.show_out(show_out)
|
|
48
|
-
|
|
45
|
+
def parallelize(collection: list, max_workers: int = 0, samples = sys.maxsize, msg: str = None, action: str = 'action'):
|
|
49
46
|
if not max_workers:
|
|
50
47
|
max_workers = Config().action_workers(action, 0)
|
|
51
|
-
if
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
rs = [future.result() for future in as_completed(futures)]
|
|
64
|
-
if post:
|
|
65
|
-
rs = [post(r, show_out=show_out) for r in rs]
|
|
66
|
-
|
|
67
|
-
return rs
|
|
68
|
-
finally:
|
|
69
|
-
if KubeContext.show_parallelism():
|
|
70
|
-
log2(f"Parallel {action} elapsed time: {elapsed_time(start_time)} with {max_workers} workers")
|
|
71
|
-
else:
|
|
72
|
-
results: list[T] = []
|
|
73
|
-
|
|
74
|
-
samples = 1 if on_any else Config().action_node_samples(action, sys.maxsize)
|
|
75
|
-
l = min(len(pods), samples)
|
|
76
|
-
adj = 'all'
|
|
77
|
-
if l < len(pods):
|
|
78
|
-
adj = f'{l} sample'
|
|
79
|
-
if show_out:
|
|
80
|
-
log2(f'Executing on {adj} nodes from statefulset...')
|
|
81
|
-
for pod_name in pods:
|
|
82
|
-
try:
|
|
83
|
-
# disable stdout from the pod_exec, then show the output in a for loop
|
|
84
|
-
result = body(None, pod_name, namespace, False)
|
|
85
|
-
if post:
|
|
86
|
-
result = post(result, show_out=show_out)
|
|
87
|
-
results.append(result)
|
|
88
|
-
if result:
|
|
89
|
-
l -= 1
|
|
90
|
-
if not l:
|
|
91
|
-
break
|
|
92
|
-
except Exception as e:
|
|
93
|
-
log2(e)
|
|
94
|
-
|
|
95
|
-
return results
|
|
96
|
-
|
|
97
|
-
def exec(pod_name: str, container: str, namespace: str, command: str, show_out = True, throw_err = False, shell = '/bin/sh',
|
|
98
|
-
interaction: Callable[[any, list[str]], any] = None):
|
|
48
|
+
if samples == sys.maxsize:
|
|
49
|
+
samples = Config().action_node_samples(action, sys.maxsize)
|
|
50
|
+
|
|
51
|
+
return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
|
|
52
|
+
|
|
53
|
+
def exec(pod_name: str, container: str, namespace: str, command: str,
|
|
54
|
+
show_out = True, throw_err = False, shell = '/bin/sh',
|
|
55
|
+
background = False,
|
|
56
|
+
log_file = None,
|
|
57
|
+
interaction: Callable[[any, list[str]], any] = None,
|
|
58
|
+
env_prefix: str = None):
|
|
99
59
|
if _TEST_POD_EXEC_OUTS:
|
|
100
60
|
return _TEST_POD_EXEC_OUTS
|
|
101
61
|
|
|
@@ -103,11 +63,13 @@ class Pods:
|
|
|
103
63
|
|
|
104
64
|
api = client.CoreV1Api()
|
|
105
65
|
|
|
106
|
-
log_file = None
|
|
107
66
|
tty = True
|
|
108
67
|
exec_command = [shell, '-c', command]
|
|
109
|
-
if
|
|
110
|
-
|
|
68
|
+
if env_prefix:
|
|
69
|
+
exec_command = [shell, '-c', f'{env_prefix} {command}']
|
|
70
|
+
|
|
71
|
+
if background or command.endswith(' &'):
|
|
72
|
+
# should be false for starting a background process
|
|
111
73
|
tty = False
|
|
112
74
|
|
|
113
75
|
if Config().get('repl.background-process.auto-nohup', True):
|
|
@@ -116,15 +78,18 @@ class Pods:
|
|
|
116
78
|
if command.startswith('nodetool '):
|
|
117
79
|
cmd_name = f".{'_'.join(command.split(' ')[5:])}"
|
|
118
80
|
|
|
119
|
-
|
|
81
|
+
if not log_file:
|
|
82
|
+
log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
|
|
120
83
|
command = f"nohup {command} > {log_file} 2>&1 &"
|
|
84
|
+
if env_prefix:
|
|
85
|
+
command = f'{env_prefix} {command}'
|
|
121
86
|
exec_command = [shell, '-c', command]
|
|
122
87
|
|
|
123
88
|
k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
|
|
124
|
-
if
|
|
125
|
-
|
|
89
|
+
if Config().is_debug():
|
|
90
|
+
log2(k_command)
|
|
126
91
|
|
|
127
|
-
resp = stream(
|
|
92
|
+
resp: WSClient = stream(
|
|
128
93
|
api.connect_get_namespaced_pod_exec,
|
|
129
94
|
pod_name,
|
|
130
95
|
namespace,
|
|
@@ -137,6 +102,7 @@ class Pods:
|
|
|
137
102
|
_preload_content=False,
|
|
138
103
|
)
|
|
139
104
|
|
|
105
|
+
s: WebSocket = resp.sock
|
|
140
106
|
stdout = []
|
|
141
107
|
stderr = []
|
|
142
108
|
error_output = None
|
|
@@ -167,9 +133,49 @@ class Pods:
|
|
|
167
133
|
log2(e)
|
|
168
134
|
finally:
|
|
169
135
|
resp.close()
|
|
136
|
+
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
137
|
+
try:
|
|
138
|
+
s.sock.close()
|
|
139
|
+
except:
|
|
140
|
+
pass
|
|
170
141
|
|
|
171
142
|
return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
|
|
172
143
|
|
|
144
|
+
def read_file(pod_name: str, container: str, namespace: str, file_path: str):
|
|
145
|
+
v1 = client.CoreV1Api()
|
|
146
|
+
|
|
147
|
+
resp = stream(
|
|
148
|
+
v1.connect_get_namespaced_pod_exec,
|
|
149
|
+
name=pod_name,
|
|
150
|
+
namespace=namespace,
|
|
151
|
+
container=container,
|
|
152
|
+
command=["cat", file_path],
|
|
153
|
+
stderr=True, stdin=False,
|
|
154
|
+
stdout=True, tty=False,
|
|
155
|
+
_preload_content=False, # Important for streaming
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
s: WebSocket = resp.sock
|
|
159
|
+
try:
|
|
160
|
+
while resp.is_open():
|
|
161
|
+
resp.update(timeout=1)
|
|
162
|
+
if resp.peek_stdout():
|
|
163
|
+
yield resp.read_stdout()
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# get the exit code from server
|
|
167
|
+
error_output = resp.read_channel(ERROR_CHANNEL)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
pass
|
|
170
|
+
except Exception as e:
|
|
171
|
+
raise e
|
|
172
|
+
finally:
|
|
173
|
+
resp.close()
|
|
174
|
+
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
175
|
+
try:
|
|
176
|
+
s.sock.close()
|
|
177
|
+
except:
|
|
178
|
+
pass
|
|
173
179
|
def get_container(namespace: str, pod_name: str, container_name: str):
|
|
174
180
|
pod = Pods.get(namespace, pod_name)
|
|
175
181
|
if not pod:
|
|
@@ -278,4 +284,7 @@ class Pods:
|
|
|
278
284
|
log2(' Timed Out')
|
|
279
285
|
|
|
280
286
|
def completed(namespace: str, pod_name: str):
|
|
281
|
-
return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
|
|
287
|
+
return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
|
|
288
|
+
|
|
289
|
+
def log_prefix():
|
|
290
|
+
return Config().get('log-prefix', '/tmp/qing')
|
adam/utils_k8s/secrets.py
CHANGED
|
@@ -6,13 +6,13 @@ from kubernetes import client
|
|
|
6
6
|
from kubernetes.client import V1Secret
|
|
7
7
|
|
|
8
8
|
from adam.config import Config
|
|
9
|
-
from adam.utils import log2
|
|
9
|
+
from adam.utils import log2, wait_log
|
|
10
10
|
|
|
11
11
|
# utility collection on secrets; methods are all static
|
|
12
12
|
class Secrets:
|
|
13
13
|
@functools.lru_cache()
|
|
14
14
|
def list_secrets(namespace: str = None, name_pattern: str = None):
|
|
15
|
-
|
|
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) -> list[T]:
|
|
66
|
-
pods = StatefulSets.pod_names(statefulset, namespace)
|
|
67
|
-
|
|
68
|
-
return Pods.on_pods(pods, namespace, body, post=post, action=action, max_workers=max_workers, show_out=show_out, on_any=on_any)
|
|
69
|
-
|
|
70
58
|
@functools.lru_cache()
|
|
71
59
|
def pod_names(ss: str, ns: str):
|
|
72
60
|
return [pod.metadata.name for pod in StatefulSets.pods(ss, ns)]
|
|
@@ -92,6 +80,7 @@ class StatefulSets:
|
|
|
92
80
|
|
|
93
81
|
return restarted, False
|
|
94
82
|
|
|
83
|
+
@functools.lru_cache()
|
|
95
84
|
def get_datacenter(sts: str, ns: str) -> str:
|
|
96
85
|
v1 = client.AppsV1Api()
|
|
97
86
|
namespace = ns
|
adam/utils_net.py
CHANGED
|
@@ -18,7 +18,7 @@ def get_my_host():
|
|
|
18
18
|
return MY_HOST
|
|
19
19
|
|
|
20
20
|
def get_ip_from_hostname(hostname):
|
|
21
|
-
|
|
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
|