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
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
from adam.commands import extract_options
|
|
1
2
|
from adam.commands.command import Command
|
|
2
3
|
from adam.commands.commands_utils import show_table
|
|
4
|
+
from adam.commands.cql.utils_cql import cassandra
|
|
3
5
|
from adam.config import Config
|
|
4
6
|
from adam.utils_k8s.statefulsets import StatefulSets
|
|
5
7
|
from adam.repl_state import ReplState, RequiredState
|
|
@@ -26,28 +28,24 @@ class ShowProcesses(Command):
|
|
|
26
28
|
if not(args := self.args(cmd)):
|
|
27
29
|
return super().run(cmd, state)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
with self.validate(args, state) as (args, state):
|
|
32
|
+
with extract_options(args, ['-s', '--show']) as (args, show_out):
|
|
33
|
+
cols = Config().get('processes.columns', 'pod,cpu-metrics,mem')
|
|
34
|
+
header = Config().get('processes.header', 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT')
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
qing_args = ['qing', 'recipe', 'with']
|
|
37
|
+
args, _, recipe_qing = Command.extract_options(args, options=qing_args)
|
|
38
|
+
if set(recipe_qing) == set(qing_args):
|
|
39
|
+
cols = Config().get('processes-qing.columns', 'pod,cpu,mem')
|
|
40
|
+
header = Config().get('processes-qing.header', 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT')
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
with cassandra(state) as pods:
|
|
43
|
+
pods.show_table(cols, header, show_out=show_out)
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
show_table(state, [state.pod], cols, header, show_output=show_output)
|
|
40
|
-
elif state.sts:
|
|
41
|
-
pod_names = [pod.metadata.name for pod in StatefulSets.pods(state.sts, state.namespace)]
|
|
42
|
-
show_table(state, pod_names, cols, header, show_output=show_output)
|
|
43
|
-
|
|
44
|
-
return state
|
|
45
|
+
return state
|
|
45
46
|
|
|
46
47
|
def completion(self, state: ReplState):
|
|
47
|
-
|
|
48
|
-
return {}
|
|
49
|
-
|
|
50
|
-
return super().completion(state)
|
|
48
|
+
return super().completion(state, {'with': {'recipe': {'metrics': {'-s': None}, 'qing': {'-s': None}}}, '-s': None})
|
|
51
49
|
|
|
52
50
|
def help(self, _: ReplState):
|
|
53
|
-
return f'{ShowProcesses.COMMAND} [-s]\t show process overview -s show commands on nodes'
|
|
51
|
+
return f'{ShowProcesses.COMMAND} [with recipe qing|metrics] [-s]\t show process overview -s show commands on nodes'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
from adam.commands import extract_options
|
|
1
2
|
from adam.commands.command import Command
|
|
2
|
-
from adam.commands.
|
|
3
|
+
from adam.commands.cql.utils_cql import cassandra
|
|
3
4
|
from adam.config import Config
|
|
4
|
-
from adam.utils_k8s.statefulsets import StatefulSets
|
|
5
5
|
from adam.repl_state import ReplState, RequiredState
|
|
6
6
|
|
|
7
7
|
class ShowStorage(Command):
|
|
@@ -26,27 +26,17 @@ class ShowStorage(Command):
|
|
|
26
26
|
if not(args := self.args(cmd)):
|
|
27
27
|
return super().run(cmd, state)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
with self.validate(args, state) as (args, state):
|
|
30
|
+
with extract_options(args, ['-s', '--show']) as (args, show_out):
|
|
31
|
+
cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
|
|
32
|
+
header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
|
|
33
|
+
with cassandra(state) as pods:
|
|
34
|
+
pods.show_table(cols, header, show_out=show_out)
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
|
|
36
|
-
header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
|
|
37
|
-
if state.pod:
|
|
38
|
-
show_table(state, [state.pod], cols, header, show_output=show_output)
|
|
39
|
-
elif state.sts:
|
|
40
|
-
pod_names = [pod.metadata.name for pod in StatefulSets.pods(state.sts, state.namespace)]
|
|
41
|
-
show_table(state, pod_names, cols, header, show_output=show_output)
|
|
42
|
-
|
|
43
|
-
return state
|
|
36
|
+
return state
|
|
44
37
|
|
|
45
38
|
def completion(self, state: ReplState):
|
|
46
|
-
|
|
47
|
-
return {}
|
|
48
|
-
|
|
49
|
-
return super().completion(state)
|
|
39
|
+
return super().completion(state, {'-s': None})
|
|
50
40
|
|
|
51
41
|
def help(self, _: ReplState):
|
|
52
42
|
return f'{ShowStorage.COMMAND} [-s]\t show storage overview -s show commands on nodes'
|
adam/commands/watch.py
CHANGED
|
@@ -8,10 +8,10 @@ from adam.commands.commands_utils import show_pods, show_rollout
|
|
|
8
8
|
from adam.config import Config
|
|
9
9
|
from adam.utils_k8s.statefulsets import StatefulSets
|
|
10
10
|
from adam.repl_state import ReplState, RequiredState
|
|
11
|
-
from adam.utils import
|
|
11
|
+
from adam.utils import log2
|
|
12
12
|
|
|
13
13
|
class Watch(Command):
|
|
14
|
-
COMMAND = 'watch'
|
|
14
|
+
COMMAND = 'watch cassandra pods'
|
|
15
15
|
|
|
16
16
|
# the singleton pattern
|
|
17
17
|
def __new__(cls, *args, **kwargs):
|
|
@@ -26,37 +26,34 @@ class Watch(Command):
|
|
|
26
26
|
return Watch.COMMAND
|
|
27
27
|
|
|
28
28
|
def required(self):
|
|
29
|
-
return RequiredState.
|
|
29
|
+
return RequiredState.NAMESPACE
|
|
30
30
|
|
|
31
31
|
def run(self, cmd: str, state: ReplState):
|
|
32
32
|
if not(args := self.args(cmd)):
|
|
33
33
|
return super().run(cmd, state)
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if not pods:
|
|
41
|
-
log2("No pods are found.")
|
|
42
|
-
return state
|
|
35
|
+
with self.validate(args, state) as (args, state):
|
|
36
|
+
pods = StatefulSets.pods(state.sts, state.namespace)
|
|
37
|
+
if not pods:
|
|
38
|
+
log2("No pods are found.")
|
|
39
|
+
return state
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
stop_event = threading.Event()
|
|
42
|
+
thread = threading.Thread(target=self.loop, args=(stop_event, state.sts, pods, state.namespace), daemon=True)
|
|
43
|
+
thread.start()
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
try:
|
|
46
|
+
log2(f"Press Ctrl+C to break.")
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
time.sleep(Config().get('watch.timeout', 3600 * 1))
|
|
49
|
+
except KeyboardInterrupt:
|
|
50
|
+
pass
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
log2("Stopping watch...")
|
|
53
|
+
stop_event.set()
|
|
54
|
+
thread.join()
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
return state
|
|
60
57
|
|
|
61
58
|
def loop(self, stop_flag: threading.Event, sts: str, pods: List[client.V1Pod], ns: str):
|
|
62
59
|
show_pods(pods, ns)
|
|
@@ -73,13 +70,13 @@ class Watch(Command):
|
|
|
73
70
|
cnt = Config().get('watch.interval', 10)
|
|
74
71
|
|
|
75
72
|
def completion(self, state: ReplState):
|
|
76
|
-
if state
|
|
77
|
-
|
|
73
|
+
if sc := super().completion(state):
|
|
74
|
+
if state.sts:
|
|
75
|
+
return sc
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
return {Watch.COMMAND: {n: None for n in StatefulSets.list_sts_names()}}
|
|
77
|
+
return super().completion(state, {n: None for n in StatefulSets.list_sts_names()})
|
|
81
78
|
|
|
82
|
-
return {
|
|
79
|
+
return {}
|
|
83
80
|
|
|
84
81
|
def help(self, _: ReplState):
|
|
85
|
-
return f'{Watch.COMMAND}\t watch pod changes'
|
|
82
|
+
return f'{Watch.COMMAND}\t watch Cassandra pod changes'
|
adam/config.py
CHANGED
|
@@ -3,13 +3,16 @@ from typing import TypeVar, cast
|
|
|
3
3
|
import yaml
|
|
4
4
|
|
|
5
5
|
from . import __version__
|
|
6
|
-
from adam.utils import copy_config_file, get_deep_keys, log2
|
|
6
|
+
from adam.utils import LogConfig, copy_config_file, get_deep_keys, log2
|
|
7
7
|
|
|
8
8
|
T = TypeVar('T')
|
|
9
9
|
|
|
10
10
|
class Config:
|
|
11
11
|
EMBEDDED_PARAMS = {}
|
|
12
12
|
|
|
13
|
+
LogConfig.is_debug = lambda: Config().is_debug()
|
|
14
|
+
LogConfig.is_debug_timing = lambda: Config().get('debugs.timings', False)
|
|
15
|
+
|
|
13
16
|
# the singleton pattern
|
|
14
17
|
def __new__(cls, *args, **kwargs):
|
|
15
18
|
if not hasattr(cls, 'instance'): cls.instance = super(Config, cls).__new__(cls)
|
|
@@ -41,10 +44,6 @@ class Config:
|
|
|
41
44
|
def is_debug(self):
|
|
42
45
|
return os.getenv('QING_DEV', 'false').lower() == 'true' or Config().get('debug', False)
|
|
43
46
|
|
|
44
|
-
def debug(self, s: None):
|
|
45
|
-
if self.is_debug():
|
|
46
|
-
log2(f'DEBUG {s}')
|
|
47
|
-
|
|
48
47
|
def get(self, key: str, default: T) -> T:
|
|
49
48
|
# params['nodetool']['status']['max-nodes']
|
|
50
49
|
d = self.params
|
|
@@ -85,12 +84,4 @@ class Config:
|
|
|
85
84
|
log2(f'incorrect path: {key}')
|
|
86
85
|
return None
|
|
87
86
|
|
|
88
|
-
return v if v else 'false'
|
|
89
|
-
|
|
90
|
-
def wait_log(self, msg: str):
|
|
91
|
-
if hasattr(self, 'wait_log_flag') and not self.wait_log_flag:
|
|
92
|
-
log2(msg)
|
|
93
|
-
self.wait_log_flag = True
|
|
94
|
-
|
|
95
|
-
def clear_wait_log_flag(self):
|
|
96
|
-
self.wait_log_flag = False
|
|
87
|
+
return v if v else 'false'
|
adam/embedded_params.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
def config():
|
|
2
|
-
return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results'}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'medusa': {'restore-auto-complete': False}, 'nodetool': {'workers': 32, 'samples': 3, 'commands_in_line': 40}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,
|
|
2
|
+
return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'medusa': {'restore-auto-complete': False}, 'nodetool': {'workers': 32, 'samples': 3, 'commands_in_line': 40}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3/*'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
|
adam/pod_exec_result.py
CHANGED
|
@@ -32,4 +32,10 @@ class PodExecResult:
|
|
|
32
32
|
except:
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
|
-
return code
|
|
35
|
+
return code
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
|
|
39
|
+
|
|
40
|
+
def __audit_extra__(self):
|
|
41
|
+
return self.log_file if self.log_file else None
|
adam/repl.py
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
import getpass
|
|
2
1
|
import os
|
|
3
|
-
import re
|
|
4
2
|
import time
|
|
5
3
|
import traceback
|
|
4
|
+
from typing import cast
|
|
6
5
|
import click
|
|
7
|
-
import concurrent
|
|
8
|
-
from prompt_toolkit.completion import NestedCompleter
|
|
9
6
|
from prompt_toolkit.key_binding import KeyBindings
|
|
10
|
-
import requests
|
|
11
7
|
|
|
12
8
|
from adam.cli_group import cli
|
|
13
|
-
from adam.commands.command import Command
|
|
9
|
+
from adam.commands.command import Command, InvalidState
|
|
14
10
|
from adam.commands.command_helpers import ClusterCommandHelper
|
|
11
|
+
from adam.commands.devices.devices import Devices
|
|
15
12
|
from adam.commands.help import Help
|
|
16
|
-
from adam.commands.postgres.postgres_session import PostgresSession
|
|
17
13
|
from adam.config import Config
|
|
14
|
+
from adam.utils_audits import Audits
|
|
18
15
|
from adam.utils_k8s.kube_context import KubeContext
|
|
19
|
-
from adam.utils_k8s.statefulsets import StatefulSets
|
|
20
16
|
from adam.log import Log
|
|
21
17
|
from adam.repl_commands import ReplCommands
|
|
22
18
|
from adam.repl_session import ReplSession
|
|
23
19
|
from adam.repl_state import ReplState
|
|
24
|
-
from adam.utils import deep_merge_dicts, deep_sort_dict, lines_to_tabular, log2
|
|
20
|
+
from adam.utils import clear_wait_log_flag, debug, deep_merge_dicts, deep_sort_dict, lines_to_tabular, log2, log_timing
|
|
25
21
|
from adam.apps import Apps
|
|
26
|
-
from adam.
|
|
22
|
+
from adam.utils_repl.repl_completer import ReplCompleter
|
|
27
23
|
from . import __version__
|
|
28
24
|
|
|
29
25
|
def enter_repl(state: ReplState):
|
|
@@ -37,65 +33,13 @@ def enter_repl(state: ReplState):
|
|
|
37
33
|
session = ReplSession().prompt_session
|
|
38
34
|
|
|
39
35
|
def prompt_msg():
|
|
40
|
-
msg =
|
|
41
|
-
if state.device == ReplState.P:
|
|
42
|
-
msg = f'{ReplState.P}:'
|
|
43
|
-
pg = PostgresSession(state.namespace, state.pg_path) if state.pg_path else None
|
|
44
|
-
if pg and pg.db:
|
|
45
|
-
msg += pg.db
|
|
46
|
-
elif pg and pg.host:
|
|
47
|
-
msg += pg.host
|
|
48
|
-
elif state.device == ReplState.A:
|
|
49
|
-
msg = f'{ReplState.A}:'
|
|
50
|
-
if state.app_env:
|
|
51
|
-
msg += state.app_env
|
|
52
|
-
if state.app_app:
|
|
53
|
-
msg += f'/{state.app_app}'
|
|
54
|
-
elif state.device == ReplState.L:
|
|
55
|
-
msg = f'{ReplState.L}:'
|
|
56
|
-
else:
|
|
57
|
-
msg = f'{ReplState.C}:'
|
|
58
|
-
if state.pod:
|
|
59
|
-
# cs-d0767a536f-cs-d0767a536f-default-sts-0
|
|
60
|
-
group = re.match(r".*?-.*?-(.*)", state.pod)
|
|
61
|
-
msg += group[1]
|
|
62
|
-
elif state.sts:
|
|
63
|
-
# cs-d0767a536f-cs-d0767a536f-default-sts
|
|
64
|
-
group = re.match(r".*?-.*?-(.*)", state.sts)
|
|
65
|
-
msg += group[1]
|
|
36
|
+
msg = state.__str__()
|
|
66
37
|
|
|
67
38
|
return f"{msg}$ " if state.bash_session else f"{msg}> "
|
|
68
39
|
|
|
69
40
|
Log.log2(f'kaqing {__version__}')
|
|
70
41
|
|
|
71
|
-
|
|
72
|
-
auto_enter = Config().get('repl.auto-enter-only-cluster', 'cluster')
|
|
73
|
-
ss = StatefulSets.list_sts_name_and_ns()
|
|
74
|
-
if not ss:
|
|
75
|
-
raise Exception("no Cassandra clusters found")
|
|
76
|
-
elif not state.sts and len(ss) == 1 and auto_enter in ['cluster', 'first-pod']:
|
|
77
|
-
cluster = ss[0]
|
|
78
|
-
state.sts = cluster[0]
|
|
79
|
-
state.namespace = cluster[1]
|
|
80
|
-
if auto_enter == 'first-pod':
|
|
81
|
-
state.pod = f'{state.sts}-0'
|
|
82
|
-
if KubeContext().in_cluster_namespace:
|
|
83
|
-
Config().wait_log(f'Moving to the only Cassandra cluster: {state.sts}...')
|
|
84
|
-
else:
|
|
85
|
-
Config().wait_log(f'Moving to the only Cassandra cluster: {state.sts}@{state.namespace}...')
|
|
86
|
-
elif state.device == ReplState.A:
|
|
87
|
-
if not state.app_env:
|
|
88
|
-
if app := Config().get('repl.auto-enter-app', 'c3/c3'):
|
|
89
|
-
if app != 'no':
|
|
90
|
-
ea = app.split('/')
|
|
91
|
-
state.app_env = ea[0]
|
|
92
|
-
if len(ea) > 1:
|
|
93
|
-
state.app_app = ea[1]
|
|
94
|
-
Config().wait_log(f'Moving to {state.app_env}/{state.app_app}...')
|
|
95
|
-
else:
|
|
96
|
-
Config().wait_log(f'Moving to {state.app_env}...')
|
|
97
|
-
elif state.device == ReplState.P:
|
|
98
|
-
Config().wait_log('Inspecting postgres database instances...')
|
|
42
|
+
Devices.device(state).enter(state)
|
|
99
43
|
|
|
100
44
|
kb = KeyBindings()
|
|
101
45
|
|
|
@@ -103,31 +47,34 @@ def enter_repl(state: ReplState):
|
|
|
103
47
|
def _(event):
|
|
104
48
|
event.app.current_buffer.text = ''
|
|
105
49
|
|
|
106
|
-
with
|
|
50
|
+
with Audits.offload() as exec:
|
|
107
51
|
# warm up AWS lambda - this log line may timeout and get lost, which is fine
|
|
108
|
-
|
|
52
|
+
exec.submit(Audits.log, 'entering kaqing repl', state.namespace, 'z', 0.0)
|
|
53
|
+
|
|
54
|
+
s0 = time.time()
|
|
109
55
|
|
|
110
56
|
# use sorted command list only for auto-completion
|
|
111
57
|
sorted_cmds = sorted(cmd_list, key=lambda cmd: cmd.command())
|
|
112
58
|
while True:
|
|
59
|
+
cmd: str = None
|
|
60
|
+
result = None
|
|
113
61
|
try:
|
|
114
|
-
completer =
|
|
62
|
+
completer = ReplCompleter.from_nested_dict({})
|
|
115
63
|
if not state.bash_session:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
completer = NestedCompleter.from_nested_dict(completions)
|
|
64
|
+
with log_timing('completion-calcs'):
|
|
65
|
+
completions = {}
|
|
66
|
+
# app commands are available only on a: drive
|
|
67
|
+
if state.device == ReplState.A and state.app_app:
|
|
68
|
+
completions = log_timing('actions', lambda: Apps(path='apps.yaml').commands())
|
|
69
|
+
|
|
70
|
+
for c in sorted_cmds:
|
|
71
|
+
try:
|
|
72
|
+
completions = log_timing(c.command(), lambda: deep_sort_dict(deep_merge_dicts(completions, c.completion(state))))
|
|
73
|
+
except:
|
|
74
|
+
log2(f'* {c.command()} command returned None completions.')
|
|
75
|
+
|
|
76
|
+
# print(json.dumps(completions, indent=4))
|
|
77
|
+
completer = ReplCompleter.from_nested_dict(completions)
|
|
131
78
|
|
|
132
79
|
cmd = session.prompt(prompt_msg(), completer=completer, key_bindings=kb)
|
|
133
80
|
s0 = time.time()
|
|
@@ -139,33 +86,38 @@ def enter_repl(state: ReplState):
|
|
|
139
86
|
|
|
140
87
|
cmd = f'bash {cmd}'
|
|
141
88
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
c_sql_tried = True
|
|
157
|
-
cmd = f'audit {cmd}'
|
|
158
|
-
cmds.run(cmd, state)
|
|
89
|
+
def targetted(state: ReplState, cmd: str):
|
|
90
|
+
if not (cmd.startswith('@') and len(arry := cmd.split(' ')) > 1):
|
|
91
|
+
return state, cmd
|
|
92
|
+
|
|
93
|
+
if state.device == ReplState.A and state.app_app or state.device == ReplState.P:
|
|
94
|
+
state.push()
|
|
95
|
+
|
|
96
|
+
state.app_pod = arry[0].strip('@')
|
|
97
|
+
cmd = ' '.join(arry[1:])
|
|
98
|
+
elif state.device == ReplState.P:
|
|
99
|
+
state.push()
|
|
100
|
+
|
|
101
|
+
state.app_pod = arry[0].strip('@')
|
|
102
|
+
cmd = ' '.join(arry[1:])
|
|
159
103
|
elif state.sts:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
104
|
+
state.push()
|
|
105
|
+
|
|
106
|
+
state.pod = arry[0].strip('@')
|
|
107
|
+
cmd = ' '.join(arry[1:])
|
|
108
|
+
|
|
109
|
+
return (state, cmd)
|
|
110
|
+
|
|
111
|
+
target, cmd = targetted(state, cmd)
|
|
112
|
+
try:
|
|
113
|
+
if cmd and cmd.strip(' ') and not (result := cmds.run(cmd, target)):
|
|
114
|
+
result = try_device_default_action(target, cmds, cmd_list, cmd)
|
|
115
|
+
except InvalidState:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
if result and type(result) is ReplState and (s := cast(ReplState, result).export_session) != state.export_session:
|
|
119
|
+
state.export_session = s
|
|
120
|
+
|
|
169
121
|
except EOFError: # Handle Ctrl+D (EOF) for graceful exit
|
|
170
122
|
break
|
|
171
123
|
except Exception as e:
|
|
@@ -173,33 +125,45 @@ def enter_repl(state: ReplState):
|
|
|
173
125
|
raise e
|
|
174
126
|
else:
|
|
175
127
|
log2(e)
|
|
176
|
-
|
|
128
|
+
debug(traceback.format_exc())
|
|
177
129
|
finally:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
130
|
+
if not state.bash_session:
|
|
131
|
+
state.pop()
|
|
132
|
+
|
|
133
|
+
clear_wait_log_flag()
|
|
134
|
+
if cmd:
|
|
135
|
+
log_timing(f'command {cmd}', s0=s0)
|
|
181
136
|
|
|
182
137
|
# offload audit logging
|
|
183
138
|
if cmd and (state.device != ReplState.L or Config().get('audit.log-audit-queries', False)):
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
'
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
139
|
+
exec.submit(Audits.log, cmd, state.namespace, state.device, time.time() - s0, get_audit_extra(result))
|
|
140
|
+
|
|
141
|
+
def try_device_default_action(state: ReplState, cmds: Command, cmd_list: list[Command], cmd: str):
|
|
142
|
+
action_taken, result = Devices.device(state).try_fallback_action(cmds, state, cmd)
|
|
143
|
+
|
|
144
|
+
if not action_taken:
|
|
145
|
+
log2(f'* Invalid command: {cmd}')
|
|
146
|
+
log2()
|
|
147
|
+
lines = [c.help(state) for c in cmd_list if c.help(state)]
|
|
148
|
+
log2(lines_to_tabular(lines, separator='\t'))
|
|
149
|
+
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
def get_audit_extra(result: any):
|
|
153
|
+
if not result:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
if type(result) is list:
|
|
157
|
+
extras = set()
|
|
158
|
+
|
|
159
|
+
for r in result:
|
|
160
|
+
if hasattr(r, '__audit_extra__') and (x := r.__audit_extra__()):
|
|
161
|
+
extras.add(x)
|
|
162
|
+
|
|
163
|
+
return ','.join(list(extras))
|
|
164
|
+
|
|
165
|
+
if hasattr(result, '__audit_extra__') and (x := result.__audit_extra__()):
|
|
166
|
+
return x
|
|
203
167
|
|
|
204
168
|
@cli.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=ClusterCommandHelper, help="Enter interactive shell.")
|
|
205
169
|
@click.option('--kubeconfig', '-k', required=False, metavar='path', help='path to kubeconfig file')
|