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.

Files changed (89) hide show
  1. adam/batch.py +1 -15
  2. adam/commands/alter_tables.py +3 -14
  3. adam/commands/app.py +3 -3
  4. adam/commands/app_ping.py +2 -2
  5. adam/commands/audit/audit.py +26 -11
  6. adam/commands/audit/audit_repair_tables.py +20 -37
  7. adam/commands/audit/audit_run.py +58 -0
  8. adam/commands/audit/show_last10.py +51 -0
  9. adam/commands/audit/show_slow10.py +50 -0
  10. adam/commands/audit/show_top10.py +49 -0
  11. adam/commands/audit/utils_show_top10.py +59 -0
  12. adam/commands/bash/bash.py +124 -0
  13. adam/commands/bash/bash_completer.py +93 -0
  14. adam/commands/cat.py +55 -0
  15. adam/commands/cd.py +23 -11
  16. adam/commands/check.py +6 -0
  17. adam/commands/code.py +60 -0
  18. adam/commands/command.py +9 -4
  19. adam/commands/commands_utils.py +1 -2
  20. adam/commands/cql/cql_completions.py +7 -3
  21. adam/commands/cql/cql_utils.py +100 -8
  22. adam/commands/cql/cqlsh.py +10 -5
  23. adam/commands/deploy/deploy.py +7 -1
  24. adam/commands/deploy/deploy_pg_agent.py +2 -2
  25. adam/commands/deploy/undeploy.py +7 -1
  26. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  27. adam/commands/devices.py +29 -0
  28. adam/commands/export/__init__.py +0 -0
  29. adam/commands/export/export.py +60 -0
  30. adam/commands/export/export_on_x.py +76 -0
  31. adam/commands/export/export_rmdbs.py +65 -0
  32. adam/commands/export/export_select.py +68 -0
  33. adam/commands/export/export_use.py +56 -0
  34. adam/commands/export/utils_export.py +253 -0
  35. adam/commands/help.py +9 -5
  36. adam/commands/issues.py +6 -0
  37. adam/commands/kubectl.py +41 -0
  38. adam/commands/login.py +6 -3
  39. adam/commands/logs.py +1 -0
  40. adam/commands/ls.py +39 -27
  41. adam/commands/medusa/medusa_show_backupjobs.py +1 -0
  42. adam/commands/nodetool.py +5 -2
  43. adam/commands/postgres/postgres.py +4 -4
  44. adam/commands/postgres/{postgres_session.py → postgres_context.py} +26 -27
  45. adam/commands/postgres/postgres_utils.py +5 -5
  46. adam/commands/postgres/psql_completions.py +1 -1
  47. adam/commands/preview_table.py +18 -32
  48. adam/commands/pwd.py +4 -3
  49. adam/commands/reaper/reaper.py +3 -0
  50. adam/commands/repair/repair.py +3 -3
  51. adam/commands/report.py +6 -0
  52. adam/commands/show/show.py +3 -1
  53. adam/commands/show/show_app_actions.py +3 -0
  54. adam/commands/show/show_app_queues.py +3 -2
  55. adam/commands/show/show_login.py +3 -0
  56. adam/config.py +1 -1
  57. adam/embedded_params.py +1 -1
  58. adam/pod_exec_result.py +7 -1
  59. adam/repl.py +121 -97
  60. adam/repl_commands.py +29 -17
  61. adam/repl_state.py +224 -44
  62. adam/sql/sql_completer.py +86 -62
  63. adam/sql/sql_state_machine.py +563 -0
  64. adam/sql/term_completer.py +3 -0
  65. adam/utils_athena.py +108 -74
  66. adam/utils_audits.py +104 -0
  67. adam/utils_export.py +42 -0
  68. adam/utils_k8s/app_clusters.py +33 -0
  69. adam/utils_k8s/app_pods.py +31 -0
  70. adam/utils_k8s/cassandra_clusters.py +4 -5
  71. adam/utils_k8s/cassandra_nodes.py +4 -4
  72. adam/utils_k8s/pods.py +42 -6
  73. adam/utils_k8s/statefulsets.py +2 -2
  74. adam/version.py +1 -1
  75. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/METADATA +1 -1
  76. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/RECORD +80 -67
  77. adam/commands/bash.py +0 -92
  78. adam/commands/cql/cql_table_completer.py +0 -8
  79. adam/commands/describe/describe.py +0 -46
  80. adam/commands/describe/describe_keyspace.py +0 -60
  81. adam/commands/describe/describe_keyspaces.py +0 -50
  82. adam/commands/describe/describe_table.py +0 -60
  83. adam/commands/describe/describe_tables.py +0 -50
  84. adam/commands/postgres/psql_table_completer.py +0 -11
  85. adam/sql/state_machine.py +0 -460
  86. /adam/commands/{describe → bash}/__init__.py +0 -0
  87. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/WHEEL +0 -0
  88. {kaqing-2.0.95.dist-info → kaqing-2.0.115.dist-info}/entry_points.txt +0 -0
  89. {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.postgres_session import PostgresSession
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 = PostgresSession(ns, pg_path)
16
- return [db['name'] for db in pg.databases() if db['owner'] == PostgresSession.default_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'] == PostgresSession.default_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 = PostgresSession(ns, pg_path)
27
+ pg = PostgresContext.apply(ns, pg_path)
28
28
  if pg.db:
29
29
  return pg.tables()
30
30
 
@@ -7,4 +7,4 @@ def psql_completions(ns: str, pg_path: str):
7
7
  '\d': None,
8
8
  '\dt': None,
9
9
  '\du': None
10
- } | SqlCompleter.completions(lambda: pg_table_names(ns, pg_path))
10
+ } | SqlCompleter(lambda: pg_table_names(ns, pg_path)).completions_for_nesting()
@@ -1,13 +1,11 @@
1
- import functools
2
-
3
1
  from adam.commands.command import Command
4
- from adam.commands.cql.cql_table_completer import CqlTableNameCompleter
5
- from adam.commands.cql.cql_utils import run_cql, table_names, tables
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 state.device == ReplState.P:
36
- if not self.validate_state(state, RequiredState.PG_DATABASE):
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 = PostgresSession(state.namespace, state.pg_path)
46
- lines = [db["name"] for db in pg.tables() if db["schema"] == PostgresSession.default_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
- run_cql(state, f'describe tables', show_out=True)
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
- PostgresSession(state.namespace, state.pg_path).run_sql(f'select * from {table} limit {rows}')
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, state: ReplState):
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.postgres_session import PostgresSession
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 = PostgresSession(state.namespace, state.pg_path)
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 == ReplState.L:
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 "/"}',
@@ -37,6 +37,9 @@ class Reaper(Command):
37
37
  if not(args := self.args(cmd)):
38
38
  return super().run(cmd, state)
39
39
 
40
+ if not self.validate_state(state):
41
+ return state
42
+
40
43
  return super().intermediate_run(cmd, state, args, Reaper.cmd_list())
41
44
 
42
45
  def cmd_list():
@@ -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
- if state.sts:
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
 
@@ -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)
@@ -20,6 +20,9 @@ class ShowAppActions(Command):
20
20
  def command(self):
21
21
  return ShowAppActions.COMMAND
22
22
 
23
+ def required(self):
24
+ return ReplState.A
25
+
23
26
  def run(self, cmd: str, state: ReplState):
24
27
  if not self.args(cmd):
25
28
  return super().run(cmd, state)
@@ -18,14 +18,15 @@ class ShowAppQueues(Command):
18
18
  return ShowAppQueues.COMMAND
19
19
 
20
20
  def required(self):
21
- return RequiredState.CLUSTER_OR_POD
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')
@@ -24,6 +24,9 @@ class ShowLogin(Command):
24
24
  def command(self):
25
25
  return ShowLogin.COMMAND
26
26
 
27
+ def required(self):
28
+ return ReplState.NON_L
29
+
27
30
  def run(self, cmd: str, state: ReplState):
28
31
  if not(args := self.args(cmd)):
29
32
  return super().run(cmd, state)
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-app': 'c3/c3', 'auto-enter-only-cluster': '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}}
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 getpass
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.postgres_session import PostgresSession
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.utils_net import get_my_host
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-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}...')
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 app := Config().get('repl.auto-enter-app', 'c3/c3'):
89
- if app != 'no':
90
- ea = app.split('/')
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) > 1:
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(audit_log, 'entering kaqing repl', state)
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 = NestedCompleter.from_nested_dict({})
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 = NestedCompleter.from_nested_dict(completions)
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
- if cmd and cmd.strip(' ') and not cmds.run(cmd, state):
143
- c_sql_tried = False
144
- if state.device == ReplState.P:
145
- pg = PostgresSession(state.namespace, state.pg_path)
146
- if pg.db:
147
- c_sql_tried = True
148
- cmd = f'pg {cmd}'
149
- cmds.run(cmd, state)
150
- elif state.device == ReplState.A:
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
- c_sql_tried = True
161
- cmd = f'cql {cmd}'
162
- cmds.run(cmd, state)
163
-
164
- if not c_sql_tried:
165
- log2(f'* Invalid command: {cmd}')
166
- log2()
167
- lines = [c.help(state) for c in cmd_list if c.help(state)]
168
- log2(lines_to_tabular(lines, separator='\t'))
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(audit_log, cmd, state)
185
-
186
- def audit_log(cmd: str, state: ReplState):
187
- payload = {
188
- 'cluster': state.namespace if state.namespace else 'NA',
189
- 'ts': time.time(),
190
- 'host': get_my_host(),
191
- 'user': getpass.getuser(),
192
- 'line': cmd.replace('"', '""').replace('\n', ' '),
193
- }
194
- audit_endpoint = Config().get("audit.endpoint", "https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/")
195
- try:
196
- response = requests.post(audit_endpoint, json=payload, timeout=Config().get("audit.timeout", 10))
197
- if response.status_code in [200, 201]:
198
- Config().debug(response.text)
199
- else:
200
- log2(f"Error: {response.status_code} {response.text}")
201
- except requests.exceptions.Timeout as e:
202
- log2(f"Timeout occurred: {e}")
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')