kaqing 2.0.14__py3-none-any.whl → 2.0.145__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 (163) hide show
  1. adam/apps.py +2 -2
  2. adam/batch.py +13 -3
  3. adam/checks/check_utils.py +4 -4
  4. adam/checks/compactionstats.py +1 -1
  5. adam/checks/cpu.py +2 -2
  6. adam/checks/disk.py +1 -1
  7. adam/checks/gossip.py +1 -1
  8. adam/checks/memory.py +3 -3
  9. adam/checks/status.py +1 -1
  10. adam/commands/alter_tables.py +81 -0
  11. adam/commands/app.py +3 -3
  12. adam/commands/app_ping.py +2 -2
  13. adam/commands/audit/audit.py +86 -0
  14. adam/commands/audit/audit_repair_tables.py +77 -0
  15. adam/commands/audit/audit_run.py +58 -0
  16. adam/commands/audit/show_last10.py +51 -0
  17. adam/commands/audit/show_slow10.py +50 -0
  18. adam/commands/audit/show_top10.py +48 -0
  19. adam/commands/audit/utils_show_top10.py +59 -0
  20. adam/commands/bash/__init__.py +0 -0
  21. adam/commands/bash/bash.py +133 -0
  22. adam/commands/bash/bash_completer.py +93 -0
  23. adam/commands/cat.py +56 -0
  24. adam/commands/cd.py +12 -82
  25. adam/commands/check.py +6 -0
  26. adam/commands/cli_commands.py +3 -3
  27. adam/commands/code.py +60 -0
  28. adam/commands/command.py +48 -12
  29. adam/commands/commands_utils.py +4 -5
  30. adam/commands/cql/__init__.py +0 -0
  31. adam/commands/cql/cql_completions.py +28 -0
  32. adam/commands/cql/cql_utils.py +209 -0
  33. adam/commands/{cqlsh.py → cql/cqlsh.py} +15 -10
  34. adam/commands/deploy/code_utils.py +2 -2
  35. adam/commands/deploy/deploy.py +8 -21
  36. adam/commands/deploy/deploy_frontend.py +1 -1
  37. adam/commands/deploy/deploy_pg_agent.py +3 -3
  38. adam/commands/deploy/deploy_pod.py +28 -27
  39. adam/commands/deploy/deploy_utils.py +16 -26
  40. adam/commands/deploy/undeploy.py +8 -21
  41. adam/commands/deploy/undeploy_frontend.py +1 -1
  42. adam/commands/deploy/undeploy_pg_agent.py +5 -3
  43. adam/commands/deploy/undeploy_pod.py +12 -10
  44. adam/commands/devices/__init__.py +0 -0
  45. adam/commands/devices/device.py +27 -0
  46. adam/commands/devices/device_app.py +146 -0
  47. adam/commands/devices/device_auit_log.py +43 -0
  48. adam/commands/devices/device_cass.py +145 -0
  49. adam/commands/devices/device_export.py +86 -0
  50. adam/commands/devices/device_postgres.py +109 -0
  51. adam/commands/devices/devices.py +25 -0
  52. adam/commands/export/__init__.py +0 -0
  53. adam/commands/export/clean_up_export_session.py +53 -0
  54. adam/commands/export/clean_up_export_sessions.py +40 -0
  55. adam/commands/export/drop_export_database.py +58 -0
  56. adam/commands/export/drop_export_databases.py +46 -0
  57. adam/commands/export/export.py +83 -0
  58. adam/commands/export/export_databases.py +170 -0
  59. adam/commands/export/export_select.py +85 -0
  60. adam/commands/export/export_select_x.py +54 -0
  61. adam/commands/export/export_use.py +55 -0
  62. adam/commands/export/exporter.py +364 -0
  63. adam/commands/export/import_session.py +68 -0
  64. adam/commands/export/importer.py +67 -0
  65. adam/commands/export/importer_athena.py +80 -0
  66. adam/commands/export/importer_sqlite.py +47 -0
  67. adam/commands/export/show_column_counts.py +63 -0
  68. adam/commands/export/show_export_databases.py +39 -0
  69. adam/commands/export/show_export_session.py +51 -0
  70. adam/commands/export/show_export_sessions.py +47 -0
  71. adam/commands/export/utils_export.py +291 -0
  72. adam/commands/help.py +12 -7
  73. adam/commands/issues.py +6 -0
  74. adam/commands/kubectl.py +41 -0
  75. adam/commands/login.py +7 -4
  76. adam/commands/logs.py +2 -1
  77. adam/commands/ls.py +4 -107
  78. adam/commands/medusa/medusa.py +2 -26
  79. adam/commands/medusa/medusa_backup.py +2 -2
  80. adam/commands/medusa/medusa_restore.py +3 -4
  81. adam/commands/medusa/medusa_show_backupjobs.py +4 -3
  82. adam/commands/medusa/medusa_show_restorejobs.py +3 -3
  83. adam/commands/nodetool.py +9 -4
  84. adam/commands/param_set.py +1 -1
  85. adam/commands/postgres/postgres.py +42 -43
  86. adam/commands/postgres/{postgres_session.py → postgres_context.py} +43 -42
  87. adam/commands/postgres/postgres_utils.py +31 -0
  88. adam/commands/postgres/psql_completions.py +10 -0
  89. adam/commands/preview_table.py +18 -40
  90. adam/commands/pwd.py +2 -28
  91. adam/commands/reaper/reaper.py +4 -24
  92. adam/commands/reaper/reaper_restart.py +1 -1
  93. adam/commands/reaper/reaper_session.py +2 -2
  94. adam/commands/repair/repair.py +3 -27
  95. adam/commands/repair/repair_log.py +1 -1
  96. adam/commands/repair/repair_run.py +2 -2
  97. adam/commands/repair/repair_scan.py +1 -1
  98. adam/commands/repair/repair_stop.py +1 -1
  99. adam/commands/report.py +6 -0
  100. adam/commands/restart.py +2 -2
  101. adam/commands/rollout.py +1 -1
  102. adam/commands/show/show.py +11 -26
  103. adam/commands/show/show_app_actions.py +3 -0
  104. adam/commands/show/show_app_id.py +1 -1
  105. adam/commands/show/show_app_queues.py +3 -2
  106. adam/commands/show/show_cassandra_status.py +3 -3
  107. adam/commands/show/show_cassandra_version.py +3 -3
  108. adam/commands/show/show_host.py +33 -0
  109. adam/commands/show/show_login.py +3 -0
  110. adam/commands/show/show_processes.py +1 -1
  111. adam/commands/show/show_repairs.py +2 -2
  112. adam/commands/show/show_storage.py +1 -1
  113. adam/commands/watch.py +1 -1
  114. adam/config.py +16 -3
  115. adam/embedded_params.py +1 -1
  116. adam/pod_exec_result.py +10 -2
  117. adam/repl.py +127 -117
  118. adam/repl_commands.py +51 -16
  119. adam/repl_state.py +276 -55
  120. adam/sql/__init__.py +0 -0
  121. adam/sql/sql_completer.py +120 -0
  122. adam/sql/sql_state_machine.py +617 -0
  123. adam/sql/term_completer.py +76 -0
  124. adam/sso/authn_ad.py +1 -1
  125. adam/sso/cred_cache.py +1 -1
  126. adam/sso/idp.py +1 -1
  127. adam/utils.py +83 -2
  128. adam/utils_athena.py +145 -0
  129. adam/utils_audits.py +102 -0
  130. adam/utils_k8s/__init__.py +0 -0
  131. adam/utils_k8s/app_clusters.py +33 -0
  132. adam/utils_k8s/app_pods.py +31 -0
  133. adam/{k8s_utils → utils_k8s}/cassandra_clusters.py +6 -21
  134. adam/{k8s_utils → utils_k8s}/cassandra_nodes.py +12 -5
  135. adam/{k8s_utils → utils_k8s}/deployment.py +2 -2
  136. adam/{k8s_utils → utils_k8s}/kube_context.py +1 -1
  137. adam/{k8s_utils → utils_k8s}/pods.py +119 -26
  138. adam/{k8s_utils → utils_k8s}/secrets.py +4 -0
  139. adam/{k8s_utils → utils_k8s}/statefulsets.py +5 -4
  140. adam/utils_net.py +24 -0
  141. adam/utils_repl/__init__.py +0 -0
  142. adam/utils_repl/automata_completer.py +48 -0
  143. adam/utils_repl/repl_completer.py +46 -0
  144. adam/utils_repl/state_machine.py +173 -0
  145. adam/utils_sqlite.py +101 -0
  146. adam/version.py +1 -1
  147. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/METADATA +1 -1
  148. kaqing-2.0.145.dist-info/RECORD +227 -0
  149. adam/commands/bash.py +0 -87
  150. adam/commands/cql_utils.py +0 -53
  151. adam/commands/devices.py +0 -89
  152. kaqing-2.0.14.dist-info/RECORD +0 -167
  153. /adam/{k8s_utils → commands/audit}/__init__.py +0 -0
  154. /adam/{k8s_utils → utils_k8s}/config_maps.py +0 -0
  155. /adam/{k8s_utils → utils_k8s}/custom_resources.py +0 -0
  156. /adam/{k8s_utils → utils_k8s}/ingresses.py +0 -0
  157. /adam/{k8s_utils → utils_k8s}/jobs.py +0 -0
  158. /adam/{k8s_utils → utils_k8s}/service_accounts.py +0 -0
  159. /adam/{k8s_utils → utils_k8s}/services.py +0 -0
  160. /adam/{k8s_utils → utils_k8s}/volumes.py +0 -0
  161. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/WHEEL +0 -0
  162. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/entry_points.txt +0 -0
  163. {kaqing-2.0.14.dist-info → kaqing-2.0.145.dist-info}/top_level.txt +0 -0
adam/commands/ls.py CHANGED
@@ -1,20 +1,8 @@
1
1
  import copy
2
- import re
3
2
 
4
3
  from adam.commands.command import Command
5
- from adam.commands.commands_utils import show_pods, show_rollout
6
- from adam.commands.cqlsh import Cqlsh
7
- from adam.commands.postgres.postgres_session import PostgresSession
8
- from adam.config import Config
9
- from adam.k8s_utils.custom_resources import CustomResources
10
- from adam.k8s_utils.ingresses import Ingresses
11
- from adam.k8s_utils.kube_context import KubeContext
12
- from adam.k8s_utils.services import Services
13
- from adam.k8s_utils.statefulsets import StatefulSets
14
- from adam.pod_exec_result import PodExecResult
4
+ from adam.commands.devices.devices import Devices
15
5
  from adam.repl_state import ReplState
16
- from adam.utils import lines_to_tabular, log, log2
17
- from adam.apps import Apps
18
6
 
19
7
  class Ls(Command):
20
8
  COMMAND = 'ls'
@@ -43,103 +31,12 @@ class Ls(Command):
43
31
  state = copy.copy(state)
44
32
  state.device = arg.replace(':', '')
45
33
 
46
- if state.device == ReplState.P:
47
- if state.pg_path:
48
- pg = PostgresSession(state.namespace, state.pg_path)
49
- if pg.db:
50
- self.show_pg_tables(pg)
51
- else:
52
- self.show_pg_databases(pg)
53
- else:
54
- self.show_pg_hosts(state)
55
- elif state.device == ReplState.A:
56
- if state.app_env:
57
- def line(n: str, ns: str):
58
- host = Ingresses.get_host(Config().get('app.login.ingress', '{app_id}-k8singr-appleader-001').replace('{app_id}', f'{ns}-{n}'), ns)
59
- if not host:
60
- return None
61
-
62
- endpoint = Config().get('app.login.url', 'https://{host}/{env}/{app}').replace('{host}', host).replace('{env}', state.app_env).replace('{app}', 'c3')
63
- if not endpoint:
64
- return None
65
-
66
- return f"{n.split('-')[1]},{Ingresses.get_host(f'{ns}-{n}-k8singr-appleader-001', ns)},{endpoint}"
67
-
68
- svcs = [l for l in [line(n, ns) for n, ns in Apps.apps(state.app_env)] if l]
69
-
70
- log(lines_to_tabular(svcs, 'APP,HOST,ENDPOINT', separator=','))
71
- else:
72
- svcs = [n for n, ns in Apps.envs()]
73
-
74
- log(lines_to_tabular(svcs, 'ENV', separator=','))
75
- else:
76
- if state.pod:
77
- r: PodExecResult = Cqlsh().run(f'cql describe tables', state)
78
- if r.stderr:
79
- log(r.stderr)
80
- log(r.stdout)
81
- elif state.sts and state.namespace:
82
- show_pods(StatefulSets.pods(state.sts, state.namespace), state.namespace, show_namespace=not KubeContext.in_cluster_namespace())
83
- show_rollout(state.sts, state.namespace)
84
- else:
85
- self.show_statefulsets()
34
+ Devices.device(state).ls(cmd, state)
86
35
 
87
36
  return state
88
37
 
89
- def show_statefulsets(self):
90
- ss = StatefulSets.list_sts_names(show_namespace=not KubeContext.in_cluster_namespace())
91
- if len(ss) == 0:
92
- log2('No cassandra statefulsets found.')
93
- return
94
-
95
- app_ids = CustomResources.get_app_ids()
96
- list = []
97
- for s in ss:
98
- cr_name = CustomResources.get_cr_name(s)
99
- app_id = 'Unknown'
100
- if cr_name in app_ids:
101
- app_id = app_ids[cr_name]
102
- list.append(f"{s} {app_id}")
103
-
104
- header = 'STATEFULSET_NAME@NAMESPACE APP_ID'
105
- if KubeContext.in_cluster_namespace():
106
- header = 'STATEFULSET_NAME APP_ID'
107
- log(lines_to_tabular(list, header))
108
-
109
- def show_pg_hosts(self, state: ReplState):
110
- if state.namespace:
111
- def line(pg: PostgresSession):
112
- return f'{pg.directory()},{pg.endpoint()}:{pg.port()},{pg.username()},{pg.password()}'
113
-
114
- lines = [line(PostgresSession(state.namespace, pg)) for pg in PostgresSession.hosts(state.namespace)]
115
-
116
- log(lines_to_tabular(lines, 'NAME,ENDPOINT,USERNAME,PASSWORD', separator=','))
117
- else:
118
- def line(pg: PostgresSession):
119
- return f'{pg.directory()},{pg.namespace},{pg.endpoint()}:{pg.port()},{pg.username()},{pg.password()}'
120
-
121
- lines = [line(PostgresSession(state.namespace, pg)) for pg in PostgresSession.hosts(state.namespace)]
122
-
123
- log(lines_to_tabular(lines, 'NAME,NAMESPACE,ENDPOINT,USERNAME,PASSWORD', separator=','))
124
-
125
- def show_pg_databases(self, pg: PostgresSession):
126
- lines = [db["name"] for db in pg.databases() if db["owner"] == PostgresSession.default_owner()]
127
-
128
- log(lines_to_tabular(lines, 'DATABASE', separator=','))
129
-
130
- def show_pg_tables(self, pg: PostgresSession):
131
- lines = [db["name"] for db in pg.tables() if db["schema"] == PostgresSession.default_schema()]
132
-
133
- log(lines_to_tabular(lines, 'NAME', separator=','))
134
-
135
38
  def completion(self, state: ReplState):
136
- if state.pod:
137
- return {}
138
-
139
- if not state.sts:
140
- return {Ls.COMMAND: {n: None for n in StatefulSets.list_sts_names()}}
141
-
142
- return {Ls.COMMAND: None}
39
+ return Devices.device(state).ls_completion(Ls.COMMAND, state, default = super().completion(state))
143
40
 
144
41
  def help(self, _: ReplState):
145
- return f'{Ls.COMMAND} [device:]\t list apps, envs, clusters, nodes, pg hosts or pg databases'
42
+ return f'{Ls.COMMAND} [device:]\t list apps, envs, clusters, nodes, pg hosts/databases or export databases'
@@ -1,13 +1,11 @@
1
1
  import click
2
2
 
3
3
  from adam.commands.command import Command
4
- from adam.commands.command_helpers import ClusterCommandHelper
5
4
  from .medusa_backup import MedusaBackup
6
5
  from .medusa_restore import MedusaRestore
7
6
  from .medusa_show_backupjobs import MedusaShowBackupJobs
8
7
  from .medusa_show_restorejobs import MedusaShowRestoreJobs
9
8
  from adam.repl_state import ReplState, RequiredState
10
- from adam.utils import lines_to_tabular, log, log2
11
9
 
12
10
  class Medusa(Command):
13
11
  COMMAND = 'medusa'
@@ -31,20 +29,7 @@ class Medusa(Command):
31
29
  if not(args := self.args(cmd)):
32
30
  return super().run(cmd, state)
33
31
 
34
- state, args = self.apply_state(args, state)
35
- if not self.validate_state(state):
36
- return state
37
-
38
- if state.in_repl:
39
- log(lines_to_tabular([c.help(ReplState()) for c in Medusa.cmd_list()], separator=':'))
40
-
41
- return 'command-missing'
42
- else:
43
- # head with the Chain of Responsibility pattern
44
- cmds = Command.chain(Medusa.cmd_list())
45
- if not cmds.run(cmd, state):
46
- log2('* Command is missing.')
47
- Command.display_help()
32
+ return super().intermediate_run(cmd, state, args, Medusa.cmd_list())
48
33
 
49
34
  def cmd_list():
50
35
  return [MedusaBackup(), MedusaRestore(), MedusaShowBackupJobs(), MedusaShowRestoreJobs()]
@@ -55,15 +40,6 @@ class Medusa(Command):
55
40
 
56
41
  return {}
57
42
 
58
- def help(self, _: ReplState):
59
- return None
60
-
61
43
  class MedusaCommandHelper(click.Command):
62
44
  def get_help(self, ctx: click.Context):
63
- log(super().get_help(ctx))
64
- log()
65
- log('Sub-Commands:')
66
-
67
- log(lines_to_tabular([c.help(ReplState()).replace(f'{Medusa.COMMAND} ', ' ', 1) for c in Medusa.cmd_list()], separator=':'))
68
- log()
69
- ClusterCommandHelper.cluster_help()
45
+ Command.intermediate_help(super().get_help(ctx), Medusa.COMMAND, Medusa.cmd_list(), show_cluster_help=True)
@@ -2,9 +2,9 @@ from datetime import datetime
2
2
  import re
3
3
 
4
4
  from adam.commands.command import Command
5
- from adam.k8s_utils.statefulsets import StatefulSets
5
+ from adam.utils_k8s.statefulsets import StatefulSets
6
6
  from adam.repl_state import ReplState, RequiredState
7
- from adam.k8s_utils.custom_resources import CustomResources
7
+ from adam.utils_k8s.custom_resources import CustomResources
8
8
  from adam.utils import log2
9
9
 
10
10
 
@@ -1,9 +1,9 @@
1
1
  from datetime import datetime
2
2
 
3
3
  from adam.commands.command import Command
4
- from adam.k8s_utils.statefulsets import StatefulSets
4
+ from adam.utils_k8s.statefulsets import StatefulSets
5
5
  from adam.repl_state import ReplState, RequiredState
6
- from adam.k8s_utils.custom_resources import CustomResources
6
+ from adam.utils_k8s.custom_resources import CustomResources
7
7
  from adam.config import Config
8
8
  from adam.utils import lines_to_tabular, log2
9
9
 
@@ -59,8 +59,7 @@ class MedusaRestore(Command):
59
59
  now_dtformat = datetime.now().strftime("%Y-%m-%d.%H.%M.%S")
60
60
  rtname = 'medusa-' + now_dtformat + '-restore-from-' + bkname
61
61
  try:
62
- print('SEAN doing')
63
- # CustomResources.create_medusa_restorejob(rtname, bkname, dc, ns)
62
+ CustomResources.create_medusa_restorejob(rtname, bkname, dc, ns)
64
63
  except Exception as e:
65
64
  log2("Exception: MedusaRestore failed: %s\n" % e)
66
65
 
@@ -1,7 +1,7 @@
1
1
  from adam.commands.command import Command
2
- from adam.k8s_utils.statefulsets import StatefulSets
2
+ from adam.utils_k8s.statefulsets import StatefulSets
3
3
  from adam.repl_state import ReplState, RequiredState
4
- from adam.k8s_utils.custom_resources import CustomResources
4
+ from adam.utils_k8s.custom_resources import CustomResources
5
5
  from adam.utils import lines_to_tabular, log2
6
6
 
7
7
 
@@ -29,6 +29,7 @@ class MedusaShowBackupJobs(Command):
29
29
  state, args = self.apply_state(args, state)
30
30
  if not self.validate_state(state):
31
31
  return state
32
+
32
33
  ns = state.namespace
33
34
  dc = StatefulSets.get_datacenter(state.sts, ns)
34
35
  if not dc:
@@ -49,4 +50,4 @@ class MedusaShowBackupJobs(Command):
49
50
  return {}
50
51
 
51
52
  def help(self, _: ReplState):
52
- return f'{MedusaShowBackupJobs.COMMAND}\t show backups'
53
+ return f'{MedusaShowBackupJobs.COMMAND}\t show Medusa backups'
@@ -1,7 +1,7 @@
1
1
  from adam.commands.command import Command
2
- from adam.k8s_utils.statefulsets import StatefulSets
2
+ from adam.utils_k8s.statefulsets import StatefulSets
3
3
  from adam.repl_state import ReplState, RequiredState
4
- from adam.k8s_utils.custom_resources import CustomResources
4
+ from adam.utils_k8s.custom_resources import CustomResources
5
5
  from adam.utils import lines_to_tabular, log2
6
6
 
7
7
  class MedusaShowRestoreJobs(Command):
@@ -49,4 +49,4 @@ class MedusaShowRestoreJobs(Command):
49
49
  return {}
50
50
 
51
51
  def help(self, _: ReplState):
52
- return f'{MedusaShowRestoreJobs.COMMAND}\t show restores'
52
+ return f'{MedusaShowRestoreJobs.COMMAND}\t show Medusa restores'
adam/commands/nodetool.py CHANGED
@@ -4,10 +4,11 @@ from adam.commands.command import Command
4
4
  from adam.commands.command_helpers import ClusterOrPodCommandHelper
5
5
  from adam.commands.nodetool_commands import NODETOOL_COMMANDS
6
6
  from adam.config import Config
7
- from adam.k8s_utils.cassandra_clusters import CassandraClusters
8
- from adam.k8s_utils.cassandra_nodes import CassandraNodes
7
+ from adam.utils_k8s.cassandra_clusters import CassandraClusters
8
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
9
9
  from adam.repl_state import ReplState, RequiredState
10
10
  from adam.utils import log
11
+ from adam.utils_k8s.statefulsets import StatefulSets
11
12
 
12
13
  class NodeTool(Command):
13
14
  COMMAND = 'nodetool'
@@ -43,14 +44,18 @@ class NodeTool(Command):
43
44
  elif state.sts:
44
45
  return CassandraClusters.exec(state.sts, state.namespace, command, action='nodetool', show_out=True)
45
46
 
47
+ return state
48
+
46
49
  def completion(self, state: ReplState):
47
50
  if state.pod or state.sts:
48
- return {NodeTool.COMMAND: {'help': None} | {c: None for c in NODETOOL_COMMANDS}}
51
+ d = {c: {'&': None} for c in NODETOOL_COMMANDS}
52
+ return {NodeTool.COMMAND: {'help': None} | d} | \
53
+ {f'@{p}': {NodeTool.COMMAND: d} for p in StatefulSets.pod_names(state.sts, state.namespace)}
49
54
 
50
55
  return {}
51
56
 
52
57
  def help(self, _: ReplState):
53
- return f'{NodeTool.COMMAND} <sub-command>\t run nodetool with arguments'
58
+ return f'{NodeTool.COMMAND} <sub-command> [&]\t run nodetool with arguments'
54
59
 
55
60
  class NodeToolCommandHelper(click.Command):
56
61
  def get_help(self, ctx: click.Context):
@@ -38,7 +38,7 @@ class SetParam(Command):
38
38
  return value
39
39
 
40
40
  def completion(self, _: ReplState):
41
- return {SetParam.COMMAND: {key: None for key in Config().keys()}}
41
+ return {SetParam.COMMAND: {key: ({'true': None, 'false': None} if Config().get(key, None) in [True, False] else None) for key in Config().keys()}}
42
42
 
43
43
  def help(self, _: ReplState):
44
44
  return f"{SetParam.COMMAND} <key> <value>\t sets a Kaqing parameter to a different value"
@@ -1,12 +1,13 @@
1
1
  import click
2
2
 
3
3
  from adam.commands.command import Command
4
- from adam.commands.command_helpers import ClusterCommandHelper
4
+ from adam.commands.postgres.psql_completions import psql_completions
5
+ from adam.commands.postgres.postgres_utils import pg_table_names
5
6
  from .postgres_ls import PostgresLs
6
7
  from .postgres_preview import PostgresPreview
7
- from .postgres_session import PostgresSession
8
+ from .postgres_context import PostgresContext
8
9
  from adam.repl_state import ReplState
9
- from adam.utils import lines_to_tabular, log, log2
10
+ from adam.utils import log, log2
10
11
 
11
12
  class Postgres(Command):
12
13
  COMMAND = 'pg'
@@ -30,35 +31,27 @@ class Postgres(Command):
30
31
 
31
32
  state, args = self.apply_state(args, state)
32
33
 
33
- if state.in_repl:
34
- if not args:
34
+ if not args:
35
+ if state.in_repl:
35
36
  log2('Please use SQL statement. e.g. pg \l')
36
-
37
- return 'command-missing'
38
37
  else:
39
- self.run_sql(state, args)
40
- else:
41
- if not args:
42
38
  log2('* Command or SQL statements is missing.')
43
39
  Command.display_help()
44
40
 
45
- return 'command-missing'
46
- else:
47
- # head with the Chain of Responsibility pattern
48
- cmds = Command.chain(Postgres.cmd_list())
49
- if not cmds.run(cmd, state) :
50
- if not args:
51
- log2('* Command or SQL statements is missing.')
52
- Command.display_help()
41
+ return 'command-missing'
53
42
 
54
- return 'command-missing'
55
- else:
56
- self.run_sql(state, args)
43
+ if state.in_repl:
44
+ self.run_sql(state, args)
45
+ else:
46
+ # head with the Chain of Responsibility pattern
47
+ cmds = Command.chain(Postgres.cmd_list())
48
+ if not cmds.run(cmd, state) :
49
+ self.run_sql(state, args)
57
50
 
58
51
  return state
59
52
 
60
53
  def cmd_list():
61
- return [PostgresLs(), PostgresPreview()]
54
+ return [PostgresLs(), PostgresPreview(), PostgresPg()]
62
55
 
63
56
  def run_sql(self, state: ReplState, args: list[str]):
64
57
  if not state.pg_path:
@@ -69,21 +62,23 @@ class Postgres(Command):
69
62
 
70
63
  return state
71
64
 
72
- PostgresSession(state.namespace, state.pg_path).run_sql(' '.join(args))
65
+ background = False
66
+ if args and args[-1] == '&':
67
+ args = args[:-1]
68
+ background = True
69
+
70
+ PostgresContext.apply(state.namespace, state.pg_path).run_sql(' '.join(args), background=background)
73
71
 
74
72
  def completion(self, state: ReplState):
73
+ if state.device != state.P:
74
+ # conflicts with cql completions
75
+ return {}
76
+
75
77
  leaf = {}
76
- if PostgresSession(state.namespace, state.pg_path).db:
77
- leaf = {
78
- '\h': None,
79
- '\d': None,
80
- '\dt': None,
81
- '\du': None,
82
- 'delete': {'from': None},
83
- 'insert': {'into': None},
84
- 'select': None,
85
- 'update': None,
86
- }
78
+ session = PostgresContext.apply(state.namespace, state.pg_path)
79
+ if session.db:
80
+ if pg_table_names(state.namespace, state.pg_path):
81
+ leaf = psql_completions(state.namespace, state.pg_path)
87
82
  elif state.pg_path:
88
83
  leaf = {
89
84
  '\h': None,
@@ -96,18 +91,22 @@ class Postgres(Command):
96
91
  return {}
97
92
 
98
93
  def help(self, _: ReplState):
99
- return f'[{Postgres.COMMAND}] <sql-statements>\t run psql with queries'
94
+ return f'<sql-statements> [&]\t run queries on Postgres databases'
100
95
 
101
96
  class PostgresCommandHelper(click.Command):
102
97
  def get_help(self, ctx: click.Context):
103
- log(super().get_help(ctx))
104
- log()
105
- log('Sub-Commands:')
106
-
107
- log(lines_to_tabular([c.help(ReplState()).replace(f'{Postgres.COMMAND} ', ' ', 1) for c in Postgres.cmd_list()], separator='\t'))
108
- log()
109
- ClusterCommandHelper.cluster_help()
98
+ Command.intermediate_help(super().get_help(ctx), Postgres.COMMAND, Postgres.cmd_list(), show_cluster_help=True)
110
99
  log('PG-Name: Kubernetes secret for Postgres credentials')
111
100
  log(' e.g. stgawsscpsr-c3-c3-k8spg-cs-001')
112
101
  log('Database: Postgres database name within a host')
113
- log(' e.g. stgawsscpsr_c3_c3')
102
+ log(' e.g. stgawsscpsr_c3_c3')
103
+
104
+ # No action body, only for a help entry and auto-completion
105
+ class PostgresPg(Command):
106
+ COMMAND = 'pg'
107
+
108
+ def command(self):
109
+ return PostgresPg.COMMAND
110
+
111
+ def help(self, _: ReplState):
112
+ return f'pg <sql-statements>\t run queries on Postgres databases'
@@ -1,14 +1,37 @@
1
+ from datetime import datetime
1
2
  import functools
2
3
  import re
3
4
  import subprocess
4
5
 
5
6
  from adam.config import Config
6
- from adam.k8s_utils.kube_context import KubeContext
7
- from adam.k8s_utils.pods import Pods
8
- from adam.k8s_utils.secrets import Secrets
7
+ from adam.repl_session import ReplSession
8
+ from adam.utils_k8s.kube_context import KubeContext
9
+ from adam.utils_k8s.pods import Pods
10
+ from adam.utils_k8s.secrets import Secrets
9
11
  from adam.utils import log2
10
12
 
11
- class PostgresSession:
13
+ class PostgresContext:
14
+ def apply(namespace: str, path: str, arg: str = None) -> 'PostgresContext':
15
+ context = PostgresContext(namespace, path)
16
+
17
+ if arg:
18
+ if arg == '..':
19
+ if context.db:
20
+ context.db = None
21
+ else:
22
+ context.host = None
23
+ else:
24
+ tks = arg.split('@')
25
+ if not context.host:
26
+ context.host = tks[0]
27
+ else:
28
+ context.db = tks[0]
29
+
30
+ if not namespace and tks[1]:
31
+ context.namespace = tks[1]
32
+
33
+ return context
34
+
12
35
  def __init__(self, ns: str, path: str):
13
36
  self.namespace = ns
14
37
  self.conn_details = None
@@ -25,36 +48,7 @@ class PostgresSession:
25
48
  if len(tks) > 1:
26
49
  self.db = tks[1]
27
50
 
28
- # work for databases()
29
- def __eq__(self, other: 'PostgresSession'):
30
- return self.host == other.host
31
-
32
- def __hash__(self):
33
- return hash(self.host)
34
-
35
- def find_namespace(self, arg: str):
36
- if arg:
37
- tks = arg.split('@')
38
- if len(tks) > 1:
39
- return tks[1]
40
-
41
- return None
42
-
43
- def directory(self, arg: str = None):
44
- if arg:
45
- if arg == '..':
46
- if self.db:
47
- self.db = None
48
- else:
49
- self.host = None
50
- else:
51
- tks = arg.split('@')
52
- arg = tks[0]
53
- if not self.host:
54
- self.host = arg
55
- else:
56
- self.db = arg
57
-
51
+ def path(self):
58
52
  if not self.host:
59
53
  return None
60
54
 
@@ -65,7 +59,7 @@ class PostgresSession:
65
59
  return f'{self.host}/{self.db}'
66
60
 
67
61
  def hosts(ns: str):
68
- return PostgresSession.hosts_for_namespace(ns)
62
+ return PostgresContext.hosts_for_namespace(ns)
69
63
 
70
64
  @functools.lru_cache()
71
65
  def hosts_for_namespace(ns: str):
@@ -82,7 +76,6 @@ class PostgresSession:
82
76
 
83
77
  return [s for s in ss if not excludes(s)]
84
78
 
85
- @functools.lru_cache()
86
79
  def databases(self):
87
80
  dbs = []
88
81
  # List of databases
@@ -140,15 +133,16 @@ class PostgresSession:
140
133
 
141
134
  return dbs
142
135
 
143
- def run_sql(self, sql: str, show_out = True):
144
- db = self.db if self.db else PostgresSession.default_db()
136
+ def run_sql(self, sql: str, show_out = True, background = False):
137
+ db = self.db if self.db else PostgresContext.default_db()
145
138
 
146
139
  if KubeContext.in_cluster():
147
140
  cmd1 = f'env PGPASSWORD={self.password()} psql -h {self.endpoint()} -p {self.port()} -U {self.username()} {db} --pset pager=off -c'
148
141
  log2(f'{cmd1} "{sql}"')
149
142
  # remove double quotes from the sql argument
150
143
  cmd = cmd1.split(' ') + [sql]
151
- r = subprocess.run(cmd, capture_output=True, text=True)
144
+
145
+ r = subprocess.run(cmd, capture_output=not background, text=True)
152
146
  if show_out:
153
147
  log2(r.stdout)
154
148
  log2(r.stderr)
@@ -159,7 +153,7 @@ class PostgresSession:
159
153
  pod_name = Config().get('pg.agent.name', 'ops-pg-agent')
160
154
 
161
155
  if Config().get('pg.agent.just-in-time', False):
162
- if not PostgresSession.deploy_pg_agent(pod_name, ns):
156
+ if not PostgresContext.deploy_pg_agent(pod_name, ns):
163
157
  return
164
158
 
165
159
  real_pod_name = pod_name
@@ -169,14 +163,21 @@ class PostgresSession:
169
163
  except:
170
164
  try:
171
165
  # try with the ops pod
166
+ pod_name = Config().get('pod.name', 'ops')
172
167
  real_pod_name = Pods.get_with_selector(ns, label_selector = Config().get('pod.label-selector', 'run=ops')).metadata.name
173
168
  except:
174
169
  log2(f"Could not locate {pod_name} pod.")
175
170
  return None
176
171
 
177
- cmd = f'PGPASSWORD="{self.password()}" psql -h {self.endpoint()} -p {self.port()} -U {self.username()} {db} --pset pager=off -c "{sql}"'
172
+ cmd = f'psql -h {self.endpoint()} -p {self.port()} -U {self.username()} {db} --pset pager=off -c "{sql}"'
173
+ env_prefix = f'PGPASSWORD="{self.password()}"'
174
+
175
+ r = Pods.exec(real_pod_name, pod_name, ns, cmd, show_out=show_out, background=background, env_prefix=env_prefix)
176
+ if r and Config().get('repl.history.push-cat-remote-log-file', True):
177
+ if r.log_file and ReplSession().prompt_session:
178
+ ReplSession().prompt_session.history.append_string(f'@{r.pod} cat {r.log_file}')
178
179
 
179
- return Pods.exec(real_pod_name, pod_name, ns, cmd, show_out=show_out)
180
+ return r
180
181
 
181
182
  def deploy_pg_agent(pod_name: str, ns: str) -> str:
182
183
  image = Config().get('pg.agent.image', 'seanahnsf/kaqing')
@@ -0,0 +1,31 @@
1
+ import functools
2
+
3
+ from adam.commands.postgres.postgres_context import PostgresContext
4
+ from adam.config import Config
5
+
6
+ TestPG = [False]
7
+
8
+ @functools.lru_cache()
9
+ def pg_database_names(ns: str, pg_path: str):
10
+ if TestPG[0]:
11
+ return ['azops88_c3ai_c3']
12
+
13
+ Config().wait_log('Inspecting Postgres Databases...')
14
+
15
+ pg = PostgresContext.apply(ns, pg_path)
16
+ return [db['name'] for db in pg.databases() if db['owner'] == PostgresContext.default_owner()]
17
+
18
+ @functools.lru_cache()
19
+ def pg_table_names(ns: str, pg_path: str):
20
+ if TestPG[0]:
21
+ return ['C3_2_XYZ1']
22
+
23
+ Config().wait_log('Inspecting Postgres Database...')
24
+ return [table['name'] for table in pg_tables(ns, pg_path) if table['schema'] == PostgresContext.default_schema()]
25
+
26
+ def pg_tables(ns: str, pg_path: str):
27
+ pg = PostgresContext.apply(ns, pg_path)
28
+ if pg.db:
29
+ return pg.tables()
30
+
31
+ return []
@@ -0,0 +1,10 @@
1
+ from adam.commands.postgres.postgres_utils import pg_table_names
2
+ from adam.sql.sql_completer import SqlCompleter
3
+
4
+ def psql_completions(ns: str, pg_path: str):
5
+ return {
6
+ '\h': None,
7
+ '\d': None,
8
+ '\dt': None,
9
+ '\du': None
10
+ } | SqlCompleter(lambda: pg_table_names(ns, pg_path)).completions_for_nesting()