kaqing 2.0.95__py3-none-any.whl → 2.0.115__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/batch.py +1 -15
- adam/commands/alter_tables.py +3 -14
- adam/commands/app.py +3 -3
- adam/commands/app_ping.py +2 -2
- adam/commands/audit/audit.py +26 -11
- adam/commands/audit/audit_repair_tables.py +20 -37
- adam/commands/audit/audit_run.py +58 -0
- adam/commands/audit/show_last10.py +51 -0
- adam/commands/audit/show_slow10.py +50 -0
- adam/commands/audit/show_top10.py +49 -0
- adam/commands/audit/utils_show_top10.py +59 -0
- adam/commands/bash/bash.py +124 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/cat.py +55 -0
- adam/commands/cd.py +23 -11
- adam/commands/check.py +6 -0
- adam/commands/code.py +60 -0
- adam/commands/command.py +9 -4
- adam/commands/commands_utils.py +1 -2
- adam/commands/cql/cql_completions.py +7 -3
- adam/commands/cql/cql_utils.py +100 -8
- adam/commands/cql/cqlsh.py +10 -5
- adam/commands/deploy/deploy.py +7 -1
- adam/commands/deploy/deploy_pg_agent.py +2 -2
- adam/commands/deploy/undeploy.py +7 -1
- adam/commands/deploy/undeploy_pg_agent.py +2 -2
- adam/commands/devices.py +29 -0
- adam/commands/export/__init__.py +0 -0
- adam/commands/export/export.py +60 -0
- adam/commands/export/export_on_x.py +76 -0
- adam/commands/export/export_rmdbs.py +65 -0
- adam/commands/export/export_select.py +68 -0
- adam/commands/export/export_use.py +56 -0
- adam/commands/export/utils_export.py +253 -0
- adam/commands/help.py +9 -5
- adam/commands/issues.py +6 -0
- adam/commands/kubectl.py +41 -0
- adam/commands/login.py +6 -3
- adam/commands/logs.py +1 -0
- adam/commands/ls.py +39 -27
- adam/commands/medusa/medusa_show_backupjobs.py +1 -0
- adam/commands/nodetool.py +5 -2
- adam/commands/postgres/postgres.py +4 -4
- adam/commands/postgres/{postgres_session.py → postgres_context.py} +26 -27
- adam/commands/postgres/postgres_utils.py +5 -5
- adam/commands/postgres/psql_completions.py +1 -1
- adam/commands/preview_table.py +18 -32
- adam/commands/pwd.py +4 -3
- adam/commands/reaper/reaper.py +3 -0
- adam/commands/repair/repair.py +3 -3
- adam/commands/report.py +6 -0
- adam/commands/show/show.py +3 -1
- adam/commands/show/show_app_actions.py +3 -0
- adam/commands/show/show_app_queues.py +3 -2
- adam/commands/show/show_login.py +3 -0
- adam/config.py +1 -1
- adam/embedded_params.py +1 -1
- adam/pod_exec_result.py +7 -1
- adam/repl.py +121 -97
- adam/repl_commands.py +29 -17
- adam/repl_state.py +224 -44
- adam/sql/sql_completer.py +86 -62
- adam/sql/sql_state_machine.py +563 -0
- adam/sql/term_completer.py +3 -0
- adam/utils_athena.py +108 -74
- adam/utils_audits.py +104 -0
- adam/utils_export.py +42 -0
- adam/utils_k8s/app_clusters.py +33 -0
- adam/utils_k8s/app_pods.py +31 -0
- adam/utils_k8s/cassandra_clusters.py +4 -5
- adam/utils_k8s/cassandra_nodes.py +4 -4
- adam/utils_k8s/pods.py +42 -6
- adam/utils_k8s/statefulsets.py +2 -2
- adam/version.py +1 -1
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/METADATA +1 -1
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/RECORD +80 -67
- adam/commands/bash.py +0 -92
- adam/commands/cql/cql_table_completer.py +0 -8
- adam/commands/describe/describe.py +0 -46
- adam/commands/describe/describe_keyspace.py +0 -60
- adam/commands/describe/describe_keyspaces.py +0 -50
- adam/commands/describe/describe_table.py +0 -60
- adam/commands/describe/describe_tables.py +0 -50
- adam/commands/postgres/psql_table_completer.py +0 -11
- adam/sql/state_machine.py +0 -460
- /adam/commands/{describe → bash}/__init__.py +0 -0
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/WHEEL +0 -0
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/entry_points.txt +0 -0
- {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
|
|
3
|
-
from adam.commands.postgres.
|
|
3
|
+
from adam.commands.postgres.postgres_context import PostgresContext
|
|
4
4
|
from adam.config import Config
|
|
5
5
|
|
|
6
6
|
TestPG = [False]
|
|
@@ -12,8 +12,8 @@ def pg_database_names(ns: str, pg_path: str):
|
|
|
12
12
|
|
|
13
13
|
Config().wait_log('Inspecting Postgres Databases...')
|
|
14
14
|
|
|
15
|
-
pg =
|
|
16
|
-
return [db['name'] for db in pg.databases() if db['owner'] ==
|
|
15
|
+
pg = PostgresContext.apply(ns, pg_path)
|
|
16
|
+
return [db['name'] for db in pg.databases() if db['owner'] == PostgresContext.default_owner()]
|
|
17
17
|
|
|
18
18
|
@functools.lru_cache()
|
|
19
19
|
def pg_table_names(ns: str, pg_path: str):
|
|
@@ -21,10 +21,10 @@ def pg_table_names(ns: str, pg_path: str):
|
|
|
21
21
|
return ['C3_2_XYZ1']
|
|
22
22
|
|
|
23
23
|
Config().wait_log('Inspecting Postgres Database...')
|
|
24
|
-
return [table['name'] for table in pg_tables(ns, pg_path) if table['schema'] ==
|
|
24
|
+
return [table['name'] for table in pg_tables(ns, pg_path) if table['schema'] == PostgresContext.default_schema()]
|
|
25
25
|
|
|
26
26
|
def pg_tables(ns: str, pg_path: str):
|
|
27
|
-
pg =
|
|
27
|
+
pg = PostgresContext.apply(ns, pg_path)
|
|
28
28
|
if pg.db:
|
|
29
29
|
return pg.tables()
|
|
30
30
|
|
adam/commands/preview_table.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
|
|
3
1
|
from adam.commands.command import Command
|
|
4
|
-
from adam.commands.cql.
|
|
5
|
-
from adam.commands.
|
|
6
|
-
from adam.commands.postgres.postgres_session import PostgresSession
|
|
7
|
-
from adam.commands.postgres.psql_table_completer import PsqlTableNameCompleter
|
|
2
|
+
from adam.commands.cql.cql_utils import cassandra_table_names, run_cql
|
|
3
|
+
from adam.commands.postgres.postgres_context import PostgresContext
|
|
8
4
|
from adam.config import Config
|
|
9
5
|
from adam.repl_state import ReplState, RequiredState
|
|
10
6
|
from adam.utils import lines_to_tabular, log, log2
|
|
7
|
+
from adam.utils_athena import Athena
|
|
8
|
+
from adam.utils_audits import Audits
|
|
11
9
|
|
|
12
10
|
class PreviewTable(Command):
|
|
13
11
|
COMMAND = 'preview'
|
|
@@ -25,28 +23,26 @@ class PreviewTable(Command):
|
|
|
25
23
|
return PreviewTable.COMMAND
|
|
26
24
|
|
|
27
25
|
def required(self):
|
|
28
|
-
return RequiredState.CLUSTER_OR_POD
|
|
26
|
+
return [RequiredState.CLUSTER_OR_POD, RequiredState.PG_DATABASE, ReplState.L]
|
|
29
27
|
|
|
30
28
|
def run(self, cmd: str, state: ReplState):
|
|
31
29
|
if not(args := self.args(cmd)):
|
|
32
30
|
return super().run(cmd, state)
|
|
33
31
|
|
|
34
32
|
state, args = self.apply_state(args, state)
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
return state
|
|
38
|
-
else:
|
|
39
|
-
if not self.validate_state(state):
|
|
40
|
-
return state
|
|
33
|
+
if not self.validate_state(state):
|
|
34
|
+
return state
|
|
41
35
|
|
|
42
36
|
if not args:
|
|
43
37
|
def show_tables():
|
|
44
38
|
if state.device == ReplState.P:
|
|
45
|
-
pg =
|
|
46
|
-
lines = [db["name"] for db in pg.tables() if db["schema"] ==
|
|
39
|
+
pg = PostgresContext.apply(state.namespace, state.pg_path)
|
|
40
|
+
lines = [db["name"] for db in pg.tables() if db["schema"] == PostgresContext.default_schema()]
|
|
47
41
|
log(lines_to_tabular(lines, separator=','))
|
|
42
|
+
elif state.device == ReplState.L:
|
|
43
|
+
log(lines_to_tabular(Athena.table_names(), separator=','))
|
|
48
44
|
else:
|
|
49
|
-
|
|
45
|
+
log(lines_to_tabular(cassandra_table_names(state), separator=','))
|
|
50
46
|
|
|
51
47
|
if state.in_repl:
|
|
52
48
|
log2('Table is required.')
|
|
@@ -65,26 +61,16 @@ class PreviewTable(Command):
|
|
|
65
61
|
|
|
66
62
|
rows = Config().get('preview.rows', 10)
|
|
67
63
|
if state.device == ReplState.P:
|
|
68
|
-
|
|
64
|
+
PostgresContext.apply(state.namespace, state.pg_path).run_sql(f'select * from {table} limit {rows}')
|
|
65
|
+
elif state.device == ReplState.L:
|
|
66
|
+
Athena.run_query(f'select * from {table} limit {rows}')
|
|
69
67
|
else:
|
|
70
|
-
run_cql(state, f'select * from {table} limit {rows}', show_out=True, use_single_quotes=True)
|
|
68
|
+
run_cql(state, f'select * from {table} limit {rows}', show_out=True, use_single_quotes=True, on_any=True)
|
|
71
69
|
|
|
72
70
|
return state
|
|
73
71
|
|
|
74
|
-
def completion(self,
|
|
75
|
-
if state.device == ReplState.P:
|
|
76
|
-
return {PreviewTable.COMMAND: PsqlTableNameCompleter(state.namespace, state.pg_path)}
|
|
77
|
-
elif state.sts:
|
|
78
|
-
return {PreviewTable.COMMAND: CqlTableNameCompleter(table_names(state))}
|
|
79
|
-
|
|
72
|
+
def completion(self, _: ReplState):
|
|
80
73
|
return {}
|
|
81
74
|
|
|
82
75
|
def help(self, _: ReplState):
|
|
83
|
-
return f'{PreviewTable.COMMAND} TABLE\t preview table'
|
|
84
|
-
|
|
85
|
-
@functools.lru_cache()
|
|
86
|
-
def cql_tables(state: ReplState):
|
|
87
|
-
if state.pod:
|
|
88
|
-
return tables(state)
|
|
89
|
-
|
|
90
|
-
return tables(state, on_any=True)
|
|
76
|
+
return f'{PreviewTable.COMMAND} TABLE\t preview table'
|
adam/commands/pwd.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from adam.app_session import AppSession
|
|
2
2
|
from adam.commands.command import Command
|
|
3
|
-
from adam.commands.postgres.
|
|
3
|
+
from adam.commands.postgres.postgres_context import PostgresContext
|
|
4
4
|
from adam.repl_state import ReplState
|
|
5
5
|
from adam.utils import lines_to_tabular, log
|
|
6
6
|
|
|
@@ -29,7 +29,7 @@ class Pwd(Command):
|
|
|
29
29
|
words = []
|
|
30
30
|
|
|
31
31
|
if device == ReplState.P:
|
|
32
|
-
pg =
|
|
32
|
+
pg: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path)
|
|
33
33
|
|
|
34
34
|
if pg.host:
|
|
35
35
|
words.append(f'host/{pg.host}')
|
|
@@ -40,7 +40,7 @@ class Pwd(Command):
|
|
|
40
40
|
words.append(f'env/{state.app_env}')
|
|
41
41
|
if state.app_app:
|
|
42
42
|
words.append(f'app/{state.app_app}')
|
|
43
|
-
elif device
|
|
43
|
+
elif device in [ReplState.L, ReplState.X]:
|
|
44
44
|
pass
|
|
45
45
|
else:
|
|
46
46
|
if state.sts:
|
|
@@ -62,6 +62,7 @@ class Pwd(Command):
|
|
|
62
62
|
device_line(state, ReplState.C),
|
|
63
63
|
device_line(state, ReplState.L),
|
|
64
64
|
device_line(state, ReplState.P),
|
|
65
|
+
device_line(state, ReplState.X),
|
|
65
66
|
f'',
|
|
66
67
|
f'HOST\t{host}',
|
|
67
68
|
f'NAMESPACE\t{state.namespace if state.namespace else "/"}',
|
adam/commands/reaper/reaper.py
CHANGED
adam/commands/repair/repair.py
CHANGED
|
@@ -28,6 +28,8 @@ class Repair(Command):
|
|
|
28
28
|
def run(self, cmd: str, state: ReplState):
|
|
29
29
|
if not(args := self.args(cmd)):
|
|
30
30
|
return super().run(cmd, state)
|
|
31
|
+
if not self.validate_state(state):
|
|
32
|
+
return state
|
|
31
33
|
|
|
32
34
|
return super().intermediate_run(cmd, state, args, Repair.cmd_list())
|
|
33
35
|
|
|
@@ -35,9 +37,7 @@ class Repair(Command):
|
|
|
35
37
|
return [RepairRun(), RepairScan(), RepairStop(), RepairLog()]
|
|
36
38
|
|
|
37
39
|
def completion(self, state: ReplState):
|
|
38
|
-
|
|
39
|
-
return super().completion(state)
|
|
40
|
-
return {}
|
|
40
|
+
return super().completion(state)
|
|
41
41
|
|
|
42
42
|
class RepairCommandHelper(click.Command):
|
|
43
43
|
def get_help(self, ctx: click.Context):
|
adam/commands/report.py
CHANGED
|
@@ -22,12 +22,18 @@ class Report(Command):
|
|
|
22
22
|
def command(self):
|
|
23
23
|
return Report.COMMAND
|
|
24
24
|
|
|
25
|
+
def required(self):
|
|
26
|
+
return ReplState.NON_L
|
|
27
|
+
|
|
25
28
|
def run(self, cmd: str, state: ReplState):
|
|
26
29
|
if not(args := self.args(cmd)):
|
|
27
30
|
return super().run(cmd, state)
|
|
28
31
|
|
|
29
32
|
output: dict[str, any] = {}
|
|
30
33
|
state, args = self.apply_state(args, state)
|
|
34
|
+
if not self.validate_state(state):
|
|
35
|
+
return state
|
|
36
|
+
|
|
31
37
|
if state.in_repl:
|
|
32
38
|
args, show = Command.extract_options(args, ['-s', '--show'])
|
|
33
39
|
|
adam/commands/show/show.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
+
from adam.commands.audit.show_last10 import ShowLast10
|
|
3
4
|
from adam.commands.command import Command
|
|
4
5
|
from adam.commands.medusa.medusa_show_backupjobs import MedusaShowBackupJobs
|
|
5
6
|
from adam.commands.medusa.medusa_show_restorejobs import MedusaShowRestoreJobs
|
|
@@ -42,7 +43,8 @@ class Show(Command):
|
|
|
42
43
|
def cmd_list():
|
|
43
44
|
return [ShowAppActions(), ShowAppId(), ShowAppQueues(), ShowHost(), ShowLogin(), ShowKubectlCommands(),
|
|
44
45
|
ShowParams(), ShowProcesses(), ShowRepairs(), ShowStorage(), ShowAdam(),
|
|
45
|
-
ShowCassandraStatus(), ShowCassandraVersion(), MedusaShowRestoreJobs(), MedusaShowBackupJobs()
|
|
46
|
+
ShowCassandraStatus(), ShowCassandraVersion(), MedusaShowRestoreJobs(), MedusaShowBackupJobs(),
|
|
47
|
+
ShowLast10()]
|
|
46
48
|
|
|
47
49
|
def completion(self, state: ReplState):
|
|
48
50
|
return super().completion(state)
|
|
@@ -18,14 +18,15 @@ class ShowAppQueues(Command):
|
|
|
18
18
|
return ShowAppQueues.COMMAND
|
|
19
19
|
|
|
20
20
|
def required(self):
|
|
21
|
-
return RequiredState.
|
|
21
|
+
return RequiredState.APP_APP
|
|
22
22
|
|
|
23
23
|
def run(self, cmd: str, state: ReplState):
|
|
24
24
|
if not(args := self.args(cmd)):
|
|
25
25
|
return super().run(cmd, state)
|
|
26
26
|
|
|
27
27
|
state, args = self.apply_state(args, state)
|
|
28
|
-
if not self.validate_state(state, app_required=RequiredState.APP_APP):
|
|
28
|
+
# if not self.validate_state(state, app_required=RequiredState.APP_APP):
|
|
29
|
+
if not self.validate_state(state):
|
|
29
30
|
return state
|
|
30
31
|
|
|
31
32
|
_, forced = Command.extract_options(args, '--force')
|
adam/commands/show/show_login.py
CHANGED
adam/config.py
CHANGED
|
@@ -39,7 +39,7 @@ class Config:
|
|
|
39
39
|
return get_deep_keys(self.params)
|
|
40
40
|
|
|
41
41
|
def is_debug(self):
|
|
42
|
-
return os.getenv('QING_DEV').lower() == 'true' or Config().get('debug', False)
|
|
42
|
+
return os.getenv('QING_DEV', 'false').lower() == 'true' or Config().get('debug', False)
|
|
43
43
|
|
|
44
44
|
def debug(self, s: None):
|
|
45
45
|
if self.is_debug():
|
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,CPU,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': 'a', 'auto-enter
|
|
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, 'temp_dir': '/c3/cassandra/tmp', 'columns': '<keys>'}, '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,CPU,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': 'a', 'a': {'auto-enter': 'c3/c3/*'}, 'c': {'auto-enter': 'cluster'}, '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,20 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
from copy import copy
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
4
|
import time
|
|
5
5
|
import traceback
|
|
6
|
+
from typing import cast
|
|
6
7
|
import click
|
|
7
8
|
import concurrent
|
|
8
|
-
from prompt_toolkit.completion import NestedCompleter
|
|
9
9
|
from prompt_toolkit.key_binding import KeyBindings
|
|
10
|
-
import requests
|
|
11
10
|
|
|
12
11
|
from adam.cli_group import cli
|
|
13
12
|
from adam.commands.command import Command
|
|
14
13
|
from adam.commands.command_helpers import ClusterCommandHelper
|
|
15
14
|
from adam.commands.help import Help
|
|
16
|
-
from adam.commands.postgres.
|
|
15
|
+
from adam.commands.postgres.postgres_context import PostgresContext
|
|
17
16
|
from adam.config import Config
|
|
17
|
+
from adam.utils_audits import Audits
|
|
18
|
+
from adam.utils_k8s.app_pods import AppPods
|
|
18
19
|
from adam.utils_k8s.kube_context import KubeContext
|
|
19
20
|
from adam.utils_k8s.statefulsets import StatefulSets
|
|
20
21
|
from adam.log import Log
|
|
@@ -23,7 +24,7 @@ from adam.repl_session import ReplSession
|
|
|
23
24
|
from adam.repl_state import ReplState
|
|
24
25
|
from adam.utils import deep_merge_dicts, deep_sort_dict, lines_to_tabular, log2
|
|
25
26
|
from adam.apps import Apps
|
|
26
|
-
from adam.
|
|
27
|
+
from adam.utils_repl.repl_completer import ReplCompleter
|
|
27
28
|
from . import __version__
|
|
28
29
|
|
|
29
30
|
def enter_repl(state: ReplState):
|
|
@@ -37,59 +38,46 @@ def enter_repl(state: ReplState):
|
|
|
37
38
|
session = ReplSession().prompt_session
|
|
38
39
|
|
|
39
40
|
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]
|
|
41
|
+
msg = state.__str__()
|
|
66
42
|
|
|
67
43
|
return f"{msg}$ " if state.bash_session else f"{msg}> "
|
|
68
44
|
|
|
69
45
|
Log.log2(f'kaqing {__version__}')
|
|
70
46
|
|
|
71
47
|
if state.device == ReplState.C:
|
|
72
|
-
auto_enter = Config().get('repl.auto-enter
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
48
|
+
auto_enter = Config().get('repl.c.auto-enter', 'cluster')
|
|
49
|
+
if auto_enter and auto_enter in ['cluster', 'first-pod']:
|
|
50
|
+
ss = StatefulSets.list_sts_name_and_ns()
|
|
51
|
+
if not ss:
|
|
52
|
+
log2("No Cassandra clusters found.")
|
|
53
|
+
elif not state.sts and len(ss) == 1:
|
|
54
|
+
cluster = ss[0]
|
|
55
|
+
state.sts = cluster[0]
|
|
56
|
+
state.namespace = cluster[1]
|
|
57
|
+
if auto_enter == 'first-pod':
|
|
58
|
+
state.pod = f'{state.sts}-0'
|
|
59
|
+
if KubeContext().in_cluster_namespace:
|
|
60
|
+
Config().wait_log(f'Moving to the only Cassandra cluster: {state.sts}...')
|
|
61
|
+
else:
|
|
62
|
+
Config().wait_log(f'Moving to the only Cassandra cluster: {state.sts}@{state.namespace}...')
|
|
86
63
|
elif state.device == ReplState.A:
|
|
87
64
|
if not state.app_env:
|
|
88
|
-
if
|
|
89
|
-
if
|
|
90
|
-
ea =
|
|
65
|
+
if auto_enter := Config().get('repl.a.auto-enter-app', 'c3/c3/*'):
|
|
66
|
+
if auto_enter != 'no':
|
|
67
|
+
ea = auto_enter.split('/')
|
|
91
68
|
state.app_env = ea[0]
|
|
92
|
-
if len(ea) >
|
|
69
|
+
if len(ea) > 2:
|
|
70
|
+
state.app_app = ea[1]
|
|
71
|
+
state.app_pod = ea[2]
|
|
72
|
+
if state.app_pod == '*':
|
|
73
|
+
if (pods := AppPods.pod_names(state.namespace, ea[0], ea[1])):
|
|
74
|
+
state.app_pod = pods[0]
|
|
75
|
+
Config().wait_log(f'Moving to {state.app_env}/{state.app_app}/{state.app_pod}...')
|
|
76
|
+
else:
|
|
77
|
+
Config().wait_log(f'No pods found, moving to {state.app_env}/{state.app_app}...')
|
|
78
|
+
else:
|
|
79
|
+
Config().wait_log(f'Moving to {state.app_env}/{state.app_app}/{state.app_pod}...')
|
|
80
|
+
elif len(ea) > 1:
|
|
93
81
|
state.app_app = ea[1]
|
|
94
82
|
Config().wait_log(f'Moving to {state.app_env}/{state.app_app}...')
|
|
95
83
|
else:
|
|
@@ -105,13 +93,16 @@ def enter_repl(state: ReplState):
|
|
|
105
93
|
|
|
106
94
|
with concurrent.futures.ThreadPoolExecutor(max_workers=Config().get('audit.workers', 3)) as executor:
|
|
107
95
|
# warm up AWS lambda - this log line may timeout and get lost, which is fine
|
|
108
|
-
executor.submit(
|
|
96
|
+
executor.submit(Audits.log, 'entering kaqing repl', state.namespace, 'z', 0.0)
|
|
97
|
+
|
|
98
|
+
s0 = time.time()
|
|
109
99
|
|
|
110
100
|
# use sorted command list only for auto-completion
|
|
111
101
|
sorted_cmds = sorted(cmd_list, key=lambda cmd: cmd.command())
|
|
112
102
|
while True:
|
|
103
|
+
result = None
|
|
113
104
|
try:
|
|
114
|
-
completer =
|
|
105
|
+
completer = ReplCompleter.from_nested_dict({})
|
|
115
106
|
if not state.bash_session:
|
|
116
107
|
completions = {}
|
|
117
108
|
# app commands are available only on a: drive
|
|
@@ -127,7 +118,7 @@ def enter_repl(state: ReplState):
|
|
|
127
118
|
log2(f'Timing auto-completion-calc {cmd.command()}: {time.time() - s1:.2f}')
|
|
128
119
|
|
|
129
120
|
# print(json.dumps(completions, indent=4))
|
|
130
|
-
completer =
|
|
121
|
+
completer = ReplCompleter.from_nested_dict(completions)
|
|
131
122
|
|
|
132
123
|
cmd = session.prompt(prompt_msg(), completer=completer, key_bindings=kb)
|
|
133
124
|
s0 = time.time()
|
|
@@ -139,33 +130,29 @@ def enter_repl(state: ReplState):
|
|
|
139
130
|
|
|
140
131
|
cmd = f'bash {cmd}'
|
|
141
132
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if state.app_app:
|
|
152
|
-
c_sql_tried = True
|
|
153
|
-
cmd = f'app {cmd}'
|
|
154
|
-
cmds.run(cmd, state)
|
|
155
|
-
elif state.device == ReplState.L:
|
|
156
|
-
c_sql_tried = True
|
|
157
|
-
cmd = f'audit {cmd}'
|
|
158
|
-
cmds.run(cmd, state)
|
|
133
|
+
def targetted(state: ReplState, cmd: str):
|
|
134
|
+
if not (cmd.startswith('@') and len(arry := cmd.split(' ')) > 1):
|
|
135
|
+
return state, cmd
|
|
136
|
+
|
|
137
|
+
if state.device == ReplState.A and state.app_app:
|
|
138
|
+
state.push()
|
|
139
|
+
|
|
140
|
+
state.app_pod = arry[0].strip('@')
|
|
141
|
+
cmd = ' '.join(arry[1:])
|
|
159
142
|
elif state.sts:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
143
|
+
state.push()
|
|
144
|
+
|
|
145
|
+
state.pod = arry[0].strip('@')
|
|
146
|
+
cmd = ' '.join(arry[1:])
|
|
147
|
+
|
|
148
|
+
return (state, cmd)
|
|
149
|
+
|
|
150
|
+
target, cmd = targetted(state, cmd)
|
|
151
|
+
if cmd and cmd.strip(' ') and not (result := cmds.run(cmd, target)):
|
|
152
|
+
result = try_device_default_action(target, cmds, cmd_list, cmd)
|
|
153
|
+
|
|
154
|
+
if result and type(result) is ReplState and (s := cast(ReplState, result).export_session):
|
|
155
|
+
state.export_session = s
|
|
169
156
|
except EOFError: # Handle Ctrl+D (EOF) for graceful exit
|
|
170
157
|
break
|
|
171
158
|
except Exception as e:
|
|
@@ -175,31 +162,68 @@ def enter_repl(state: ReplState):
|
|
|
175
162
|
log2(e)
|
|
176
163
|
Config().debug(traceback.format_exc())
|
|
177
164
|
finally:
|
|
165
|
+
if not state.bash_session:
|
|
166
|
+
state.pop()
|
|
167
|
+
|
|
178
168
|
Config().clear_wait_log_flag()
|
|
179
169
|
if Config().get('debugs.timings', False) and 'cmd' in locals() and 's0' in locals():
|
|
180
170
|
log2(f'Timing command {cmd}: {time.time() - s0:.2f}')
|
|
181
171
|
|
|
182
172
|
# offload audit logging
|
|
183
173
|
if cmd and (state.device != ReplState.L or Config().get('audit.log-audit-queries', False)):
|
|
184
|
-
executor.submit(
|
|
185
|
-
|
|
186
|
-
def
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
174
|
+
executor.submit(Audits.log, cmd, state.namespace, state.device, time.time() - s0, get_audit_extra(result))
|
|
175
|
+
|
|
176
|
+
def try_device_default_action(state: ReplState, cmds: Command, cmd_list: list[Command], cmd: str):
|
|
177
|
+
result = None
|
|
178
|
+
|
|
179
|
+
c_sql_tried = False
|
|
180
|
+
if state.device == ReplState.P:
|
|
181
|
+
pg: PostgresContext = PostgresContext.apply(state.namespace, state.pg_path)
|
|
182
|
+
if pg.db:
|
|
183
|
+
c_sql_tried = True
|
|
184
|
+
cmd = f'pg {cmd}'
|
|
185
|
+
result = cmds.run(cmd, state)
|
|
186
|
+
elif state.device == ReplState.A:
|
|
187
|
+
if state.app_app:
|
|
188
|
+
c_sql_tried = True
|
|
189
|
+
cmd = f'app {cmd}'
|
|
190
|
+
result = cmds.run(cmd, state)
|
|
191
|
+
elif state.device == ReplState.L:
|
|
192
|
+
c_sql_tried = True
|
|
193
|
+
cmd = f'audit {cmd}'
|
|
194
|
+
result = cmds.run(cmd, state)
|
|
195
|
+
elif state.device == ReplState.X:
|
|
196
|
+
c_sql_tried = True
|
|
197
|
+
cmd = f'&{cmd}'
|
|
198
|
+
result = cmds.run(cmd, state)
|
|
199
|
+
elif state.sts:
|
|
200
|
+
c_sql_tried = True
|
|
201
|
+
cmd = f'cql {cmd}'
|
|
202
|
+
result = cmds.run(cmd, state)
|
|
203
|
+
|
|
204
|
+
if not c_sql_tried:
|
|
205
|
+
log2(f'* Invalid command: {cmd}')
|
|
206
|
+
log2()
|
|
207
|
+
lines = [c.help(state) for c in cmd_list if c.help(state)]
|
|
208
|
+
log2(lines_to_tabular(lines, separator='\t'))
|
|
209
|
+
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
def get_audit_extra(result: any):
|
|
213
|
+
if not result:
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
if type(result) is list:
|
|
217
|
+
extras = set()
|
|
218
|
+
|
|
219
|
+
for r in result:
|
|
220
|
+
if hasattr(r, '__audit_extra__') and (x := r.__audit_extra__()):
|
|
221
|
+
extras.add(x)
|
|
222
|
+
|
|
223
|
+
return ','.join(list(extras))
|
|
224
|
+
|
|
225
|
+
if hasattr(result, '__audit_extra__') and (x := result.__audit_extra__()):
|
|
226
|
+
return x
|
|
203
227
|
|
|
204
228
|
@cli.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=ClusterCommandHelper, help="Enter interactive shell.")
|
|
205
229
|
@click.option('--kubeconfig', '-k', required=False, metavar='path', help='path to kubeconfig file')
|