kaqing 2.0.100__tar.gz → 2.0.110__tar.gz

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 (206) hide show
  1. {kaqing-2.0.100 → kaqing-2.0.110}/PKG-INFO +1 -1
  2. {kaqing-2.0.100 → kaqing-2.0.110}/adam/batch.py +0 -14
  3. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/app.py +2 -2
  4. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/app_ping.py +2 -2
  5. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/audit/audit.py +23 -16
  6. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/audit/audit_repair_tables.py +19 -37
  7. kaqing-2.0.110/adam/commands/audit/audit_run.py +57 -0
  8. kaqing-2.0.110/adam/commands/audit/show_last10.py +50 -0
  9. kaqing-2.0.110/adam/commands/audit/show_slow10.py +49 -0
  10. kaqing-2.0.110/adam/commands/audit/show_top10.py +48 -0
  11. kaqing-2.0.110/adam/commands/audit/utils_show_top10.py +59 -0
  12. kaqing-2.0.110/adam/commands/bash.py +150 -0
  13. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cd.py +14 -8
  14. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/check.py +6 -0
  15. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/command.py +6 -4
  16. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/commands_utils.py +1 -2
  17. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cql/cql_completions.py +4 -4
  18. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cql/cql_utils.py +6 -9
  19. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cql/cqlsh.py +6 -3
  20. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/deploy.py +7 -1
  21. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/deploy_pg_agent.py +2 -2
  22. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/undeploy.py +7 -1
  23. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/undeploy_pg_agent.py +2 -2
  24. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/help.py +7 -5
  25. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/issues.py +6 -0
  26. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/login.py +6 -3
  27. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/logs.py +1 -0
  28. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/ls.py +21 -21
  29. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/medusa/medusa_show_backupjobs.py +1 -0
  30. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/nodetool.py +1 -1
  31. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/postgres/postgres.py +3 -3
  32. kaqing-2.0.100/adam/commands/postgres/postgres_session.py → kaqing-2.0.110/adam/commands/postgres/postgres_context.py +26 -27
  33. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/postgres/postgres_utils.py +5 -5
  34. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/postgres/psql_completions.py +1 -1
  35. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/preview_table.py +17 -32
  36. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/pwd.py +2 -2
  37. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper.py +3 -0
  38. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/repair/repair.py +3 -3
  39. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/report.py +6 -0
  40. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show.py +3 -1
  41. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_app_actions.py +3 -0
  42. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_app_queues.py +3 -2
  43. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_login.py +3 -0
  44. kaqing-2.0.110/adam/embedded_params.py +2 -0
  45. {kaqing-2.0.100 → kaqing-2.0.110}/adam/pod_exec_result.py +4 -1
  46. {kaqing-2.0.100 → kaqing-2.0.110}/adam/repl.py +74 -66
  47. {kaqing-2.0.100 → kaqing-2.0.110}/adam/repl_commands.py +15 -13
  48. {kaqing-2.0.100 → kaqing-2.0.110}/adam/repl_state.py +132 -30
  49. kaqing-2.0.110/adam/sql/sql_completer.py +93 -0
  50. kaqing-2.0.110/adam/sql/sql_state_machine.py +518 -0
  51. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sql/term_completer.py +3 -0
  52. kaqing-2.0.110/adam/utils_audits.py +193 -0
  53. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/cassandra_clusters.py +4 -5
  54. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/cassandra_nodes.py +3 -3
  55. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/pods.py +12 -6
  56. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/statefulsets.py +2 -2
  57. kaqing-2.0.110/adam/version.py +5 -0
  58. {kaqing-2.0.100 → kaqing-2.0.110}/kaqing.egg-info/PKG-INFO +1 -1
  59. {kaqing-2.0.100 → kaqing-2.0.110}/kaqing.egg-info/SOURCES.txt +8 -12
  60. {kaqing-2.0.100 → kaqing-2.0.110}/setup.py +1 -1
  61. kaqing-2.0.100/adam/commands/bash.py +0 -92
  62. kaqing-2.0.100/adam/commands/cql/cql_table_completer.py +0 -8
  63. kaqing-2.0.100/adam/commands/describe/describe.py +0 -47
  64. kaqing-2.0.100/adam/commands/describe/describe_keyspace.py +0 -60
  65. kaqing-2.0.100/adam/commands/describe/describe_keyspaces.py +0 -49
  66. kaqing-2.0.100/adam/commands/describe/describe_schema.py +0 -49
  67. kaqing-2.0.100/adam/commands/describe/describe_table.py +0 -60
  68. kaqing-2.0.100/adam/commands/describe/describe_tables.py +0 -49
  69. kaqing-2.0.100/adam/commands/postgres/psql_table_completer.py +0 -11
  70. kaqing-2.0.100/adam/embedded_params.py +0 -2
  71. kaqing-2.0.100/adam/sql/sql_completer.py +0 -101
  72. kaqing-2.0.100/adam/sql/state_machine.py +0 -549
  73. kaqing-2.0.100/adam/utils_athena.py +0 -95
  74. kaqing-2.0.100/adam/utils_k8s/__init__.py +0 -0
  75. kaqing-2.0.100/adam/version.py +0 -5
  76. {kaqing-2.0.100 → kaqing-2.0.110}/README +0 -0
  77. {kaqing-2.0.100 → kaqing-2.0.110}/adam/__init__.py +0 -0
  78. {kaqing-2.0.100 → kaqing-2.0.110}/adam/app_session.py +0 -0
  79. {kaqing-2.0.100 → kaqing-2.0.110}/adam/apps.py +0 -0
  80. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/__init__.py +0 -0
  81. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/check.py +0 -0
  82. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/check_context.py +0 -0
  83. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/check_result.py +0 -0
  84. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/check_utils.py +0 -0
  85. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/compactionstats.py +0 -0
  86. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/cpu.py +0 -0
  87. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/disk.py +0 -0
  88. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/gossip.py +0 -0
  89. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/issue.py +0 -0
  90. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/memory.py +0 -0
  91. {kaqing-2.0.100 → kaqing-2.0.110}/adam/checks/status.py +0 -0
  92. {kaqing-2.0.100 → kaqing-2.0.110}/adam/cli.py +0 -0
  93. {kaqing-2.0.100 → kaqing-2.0.110}/adam/cli_group.py +0 -0
  94. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/__init__.py +0 -0
  95. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/column.py +0 -0
  96. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/columns.py +0 -0
  97. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/compactions.py +0 -0
  98. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/cpu.py +0 -0
  99. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/dir_data.py +0 -0
  100. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/dir_snapshots.py +0 -0
  101. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/gossip.py +0 -0
  102. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/host_id.py +0 -0
  103. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/memory.py +0 -0
  104. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/node_address.py +0 -0
  105. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/node_load.py +0 -0
  106. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/node_owns.py +0 -0
  107. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/node_status.py +0 -0
  108. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/node_tokens.py +0 -0
  109. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/node_utils.py +0 -0
  110. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/pod_name.py +0 -0
  111. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/volume_cassandra.py +0 -0
  112. {kaqing-2.0.100 → kaqing-2.0.110}/adam/columns/volume_root.py +0 -0
  113. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/__init__.py +0 -0
  114. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/alter_tables.py +0 -0
  115. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/audit/__init__.py +0 -0
  116. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cli_commands.py +0 -0
  117. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/command_helpers.py +0 -0
  118. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cp.py +0 -0
  119. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/cql/__init__.py +0 -0
  120. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/__init__.py +0 -0
  121. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/code_start.py +0 -0
  122. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/code_stop.py +0 -0
  123. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/code_utils.py +0 -0
  124. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/deploy_frontend.py +0 -0
  125. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/deploy_pod.py +0 -0
  126. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/deploy_utils.py +0 -0
  127. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/undeploy_frontend.py +0 -0
  128. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/deploy/undeploy_pod.py +0 -0
  129. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/devices.py +0 -0
  130. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/exit.py +0 -0
  131. {kaqing-2.0.100/adam/commands/describe → kaqing-2.0.110/adam/commands/medusa}/__init__.py +0 -0
  132. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/medusa/medusa.py +0 -0
  133. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/medusa/medusa_backup.py +0 -0
  134. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/medusa/medusa_restore.py +0 -0
  135. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/medusa/medusa_show_restorejobs.py +0 -0
  136. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/nodetool_commands.py +0 -0
  137. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/param_get.py +0 -0
  138. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/param_set.py +0 -0
  139. {kaqing-2.0.100/adam/commands/medusa → kaqing-2.0.110/adam/commands/postgres}/__init__.py +0 -0
  140. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/postgres/postgres_ls.py +0 -0
  141. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/postgres/postgres_preview.py +0 -0
  142. {kaqing-2.0.100/adam/commands/postgres → kaqing-2.0.110/adam/commands/reaper}/__init__.py +0 -0
  143. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_forward.py +0 -0
  144. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_forward_stop.py +0 -0
  145. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_restart.py +0 -0
  146. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_run_abort.py +0 -0
  147. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_runs.py +0 -0
  148. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_runs_abort.py +0 -0
  149. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_schedule_activate.py +0 -0
  150. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_schedule_start.py +0 -0
  151. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_schedule_stop.py +0 -0
  152. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_schedules.py +0 -0
  153. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_session.py +0 -0
  154. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/reaper/reaper_status.py +0 -0
  155. {kaqing-2.0.100/adam/commands/reaper → kaqing-2.0.110/adam/commands/repair}/__init__.py +0 -0
  156. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/repair/repair_log.py +0 -0
  157. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/repair/repair_run.py +0 -0
  158. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/repair/repair_scan.py +0 -0
  159. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/repair/repair_stop.py +0 -0
  160. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/restart.py +0 -0
  161. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/rollout.py +0 -0
  162. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/shell.py +0 -0
  163. {kaqing-2.0.100/adam/commands/repair → kaqing-2.0.110/adam/commands/show}/__init__.py +0 -0
  164. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_adam.py +0 -0
  165. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_app_id.py +0 -0
  166. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_cassandra_status.py +0 -0
  167. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_cassandra_version.py +0 -0
  168. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_commands.py +0 -0
  169. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_host.py +0 -0
  170. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_params.py +0 -0
  171. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_processes.py +0 -0
  172. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_repairs.py +0 -0
  173. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/show/show_storage.py +0 -0
  174. {kaqing-2.0.100 → kaqing-2.0.110}/adam/commands/watch.py +0 -0
  175. {kaqing-2.0.100 → kaqing-2.0.110}/adam/config.py +0 -0
  176. {kaqing-2.0.100 → kaqing-2.0.110}/adam/embedded_apps.py +0 -0
  177. {kaqing-2.0.100 → kaqing-2.0.110}/adam/log.py +0 -0
  178. {kaqing-2.0.100 → kaqing-2.0.110}/adam/repl_session.py +0 -0
  179. {kaqing-2.0.100/adam/commands/show → kaqing-2.0.110/adam/sql}/__init__.py +0 -0
  180. {kaqing-2.0.100/adam/sql → kaqing-2.0.110/adam/sso}/__init__.py +0 -0
  181. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/authenticator.py +0 -0
  182. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/authn_ad.py +0 -0
  183. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/authn_okta.py +0 -0
  184. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/cred_cache.py +0 -0
  185. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/id_token.py +0 -0
  186. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/idp.py +0 -0
  187. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/idp_login.py +0 -0
  188. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/idp_session.py +0 -0
  189. {kaqing-2.0.100 → kaqing-2.0.110}/adam/sso/sso_config.py +0 -0
  190. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils.py +0 -0
  191. {kaqing-2.0.100/adam/sso → kaqing-2.0.110/adam/utils_k8s}/__init__.py +0 -0
  192. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/config_maps.py +0 -0
  193. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/custom_resources.py +0 -0
  194. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/deployment.py +0 -0
  195. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/ingresses.py +0 -0
  196. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/jobs.py +0 -0
  197. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/kube_context.py +0 -0
  198. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/secrets.py +0 -0
  199. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/service_accounts.py +0 -0
  200. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/services.py +0 -0
  201. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_k8s/volumes.py +0 -0
  202. {kaqing-2.0.100 → kaqing-2.0.110}/adam/utils_net.py +0 -0
  203. {kaqing-2.0.100 → kaqing-2.0.110}/kaqing.egg-info/dependency_links.txt +0 -0
  204. {kaqing-2.0.100 → kaqing-2.0.110}/kaqing.egg-info/entry_points.txt +0 -0
  205. {kaqing-2.0.100 → kaqing-2.0.110}/kaqing.egg-info/top_level.txt +0 -0
  206. {kaqing-2.0.100 → kaqing-2.0.110}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kaqing
3
- Version: 2.0.100
3
+ Version: 2.0.110
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN
@@ -9,7 +9,6 @@ from adam.commands.command_helpers import ClusterCommandHelper, ClusterOrPodComm
9
9
  from adam.commands.cql.cqlsh import CqlCommandHelper, Cqlsh
10
10
  from adam.commands.deploy.deploy import Deploy, DeployCommandHelper
11
11
  from adam.commands.deploy.undeploy import Undeploy, UndeployCommandHelper
12
- from adam.commands.describe.describe import Describe, DescribeCommandHelper
13
12
  from adam.commands.issues import Issues
14
13
  from adam.commands.login import Login
15
14
  from adam.commands.logs import Logs
@@ -97,19 +96,6 @@ def deploy(kubeconfig: str, config: str, param: list[str], namespace: str, extra
97
96
  run_command(Deploy(), kubeconfig, config, param, None, namespace, None, extra_args)
98
97
 
99
98
 
100
- @cli.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=DescribeCommandHelper, help='Describe keyspace(s) or table(s).')
101
- @click.option('--kubeconfig', '-k', required=False, metavar='path', help='path to kubeconfig file')
102
- @click.option('--config', default='params.yaml', metavar='path', help='path to kaqing parameters file')
103
- @click.option('--param', '-v', multiple=True, metavar='<key>=<value>', help='parameter override')
104
- @click.option('--cluster', '-c', required=False, metavar='statefulset', help='Kubernetes statefulset name')
105
- @click.option('--namespace', '-n', required=False, metavar='namespace', help='Kubernetes namespace')
106
- @click.option('--pod', '-p', required=False, metavar='pod', help='Kubernetes pod name')
107
- @click.option('--all-nodes', '-a', is_flag=True, help='execute on all Cassandra nodes')
108
- @click.argument('extra_args', nargs=-1, metavar='<cluster|pod>', type=click.UNPROCESSED)
109
- def describe(kubeconfig: str, config: str, param: list[str], cluster: str, namespace: str, pod: str, all_nodes: bool, extra_args):
110
- run_command(Describe(), kubeconfig, config, param, cluster, namespace, pod, extra_args + ('&',) if all_nodes else extra_args)
111
-
112
-
113
99
  @cli.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=ClusterOrPodCommandHelper, help="Print Qing's issues.")
114
100
  @click.option('--kubeconfig', '-k', required=False, metavar='path', help='path to kubeconfig file')
115
101
  @click.option('--config', default='params.yaml', metavar='path', help='path to kaqing parameters file')
@@ -22,14 +22,14 @@ class App(Command):
22
22
  return App.COMMAND
23
23
 
24
24
  def required(self):
25
- return RequiredState.CLUSTER_OR_POD
25
+ return RequiredState.APP_APP
26
26
 
27
27
  def run(self, cmd: str, state: ReplState):
28
28
  if not(args := self.args(cmd)):
29
29
  return super().run(cmd, state)
30
30
 
31
31
  state, args = self.apply_state(args, state)
32
- if not self.validate_state(state, app_required=RequiredState.APP_APP):
32
+ if not self.validate_state(state):
33
33
  return state
34
34
 
35
35
  args, forced = Command.extract_options(args, '--force')
@@ -18,14 +18,14 @@ class AppPing(Command):
18
18
  return AppPing.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):
29
29
  return state
30
30
 
31
31
  _, forced = Command.extract_options(args, '--force')
@@ -1,12 +1,17 @@
1
1
  import click
2
2
 
3
3
  from adam.commands.audit.audit_repair_tables import AuditRepairTables
4
+ from adam.commands.audit.audit_run import AuditRun
5
+ from adam.commands.audit.show_last10 import ShowLast10
6
+ from adam.commands.audit.show_slow10 import ShowSlow10
7
+ from adam.commands.audit.show_top10 import ShowTop10
8
+ from adam.commands.audit.utils_show_top10 import show_top10_completions_for_nesting
4
9
  from adam.commands.command import Command
5
10
  from adam.config import Config
6
11
  from adam.repl_state import ReplState
7
12
  from adam.sql.sql_completer import SqlCompleter
8
13
  from adam.utils import log2
9
- from adam.utils_athena import audit_column_names, audit_table_names, run_audit_query
14
+ from adam.utils_audits import Audits
10
15
 
11
16
  class Audit(Command):
12
17
  COMMAND = 'audit'
@@ -24,11 +29,16 @@ class Audit(Command):
24
29
  def command(self):
25
30
  return Audit.COMMAND
26
31
 
32
+ def required(self):
33
+ return ReplState.L
34
+
27
35
  def run(self, cmd: str, state: ReplState):
28
36
  if not(args := self.args(cmd)):
29
37
  return super().run(cmd, state)
30
38
 
31
39
  state, args = self.apply_state(args, state)
40
+ if not self.validate_state(state):
41
+ return state
32
42
 
33
43
  r = None
34
44
  if len(args) > 0:
@@ -41,7 +51,7 @@ class Audit(Command):
41
51
  else:
42
52
  log2(sql)
43
53
 
44
- run_audit_query(sql)
54
+ Audits.run_audit_query(sql)
45
55
 
46
56
  return state
47
57
 
@@ -51,27 +61,24 @@ class Audit(Command):
51
61
  Config().wait_log(f'Inspecting audit database schema...')
52
62
  self.schema_read = True
53
63
  # warm up the caches first time when l: drive is accessed
54
- audit_column_names()
55
- audit_column_names(partition_cols_only=True)
56
-
57
- # def columns(_):
58
- # return audit_column_names()
59
-
60
- return super().completion(state) | SqlCompleter.completions(
61
- lambda: audit_table_names(),
62
- columns=lambda table: audit_column_names(),
63
- partition_columns=lambda table: audit_column_names(partition_cols_only=True),
64
+ Audits.audit_table_names()
65
+ Audits.audit_column_names()
66
+ Audits.audit_column_names(partition_cols_only=True)
67
+
68
+ return super().completion(state) | show_top10_completions_for_nesting() | SqlCompleter(
69
+ lambda: Audits.audit_table_names(),
70
+ columns=lambda table: Audits.audit_column_names(),
71
+ partition_columns=lambda table: Audits.audit_column_names(partition_cols_only=True),
64
72
  variant='athena'
65
- ) | {
66
- 'desc': {table: None for table in audit_table_names()}}
73
+ ).completions_for_nesting()
67
74
 
68
75
  return {}
69
76
 
70
77
  def cmd_list():
71
- return [AuditRepairTables()]
78
+ return [AuditRepairTables(), AuditRun(), ShowLast10(), ShowSlow10(), ShowTop10()]
72
79
 
73
80
  def help(self, _: ReplState):
74
- return f'[{Audit.COMMAND}] <sql-statements>\t run SQL queries on Authena audit database'
81
+ return f'[{Audit.COMMAND}] [<sql-statements>]\t run SQL queries on Authena audit database'
75
82
 
76
83
  class AuditCommandHelper(click.Command):
77
84
  def get_help(self, ctx: click.Context):
@@ -1,12 +1,11 @@
1
1
  import concurrent
2
2
  import time
3
- import requests
4
3
 
5
4
  from adam.commands.command import Command
6
5
  from adam.config import Config
7
6
  from adam.repl_state import ReplState
8
7
  from adam.utils import log, log2
9
- from adam.utils_athena import audit_query, run_audit_query
8
+ from adam.utils_audits import AuditMeta, Audits
10
9
 
11
10
  class AuditRepairTables(Command):
12
11
  COMMAND = 'audit repair'
@@ -24,17 +23,23 @@ class AuditRepairTables(Command):
24
23
  def command(self):
25
24
  return AuditRepairTables.COMMAND
26
25
 
26
+ def required(self):
27
+ return ReplState.L
28
+
27
29
  def run(self, cmd: str, state: ReplState):
28
30
  if not(args := self.args(cmd)):
29
31
  return super().run(cmd, state)
30
32
 
31
33
  state, args = self.apply_state(args, state)
34
+ if not self.validate_state(state):
35
+ return state
32
36
 
33
- tables = Config().get('audit.tables', 'audit').split(',')
37
+ tables = Config().get('audit.athena.repair-partition-tables', 'audit').split(',')
34
38
  if args:
35
39
  tables = args
36
40
 
37
- self.repair(tables)
41
+ meta = Audits.get_meta()
42
+ self.repair(tables, meta)
38
43
 
39
44
  return state
40
45
 
@@ -52,43 +57,20 @@ class AuditRepairTables(Command):
52
57
  def auto_repair(self, hours: int):
53
58
  self.auto_repaired = True
54
59
 
55
- state, _, rs = audit_query(f'select * from meta')
56
- if state == 'SUCCEEDED':
57
- do_repair = True
58
- if len(rs) > 1:
59
- try:
60
- checked_in = float(rs[1]['Data'][0]['VarCharValue'])
61
- do_repair = checked_in + hours * 60 * 60 < time.time()
62
- except:
63
- pass
64
-
65
- if do_repair:
66
- tables = Config().get('audit.athena.tables', 'audit').split(',')
67
- self.repair(tables, show_sql=True)
68
- log2(f'Audit tables have been auto-repaired.')
69
-
70
- def repair(self, tables: list[str], show_sql = False):
60
+ meta: AuditMeta = Audits.get_meta()
61
+ if meta.partitions_last_checked + hours * 60 * 60 < time.time():
62
+ tables = Config().get('audit.athena.repair-partition-tables', 'audit').split(',')
63
+ self.repair(tables, meta, show_sql=True)
64
+ log2(f'Audit tables have been auto-repaired.')
65
+
66
+ def repair(self, tables: list[str], meta: AuditMeta, show_sql = False):
71
67
  with concurrent.futures.ThreadPoolExecutor(max_workers=Config().get('audit.workers', 3)) as executor:
72
68
  for table in tables:
73
69
  if show_sql:
74
70
  log(f'MSCK REPAIR TABLE {table}')
75
71
 
76
- executor.submit(run_audit_query, f'MSCK REPAIR TABLE {table}', None,)
77
- executor.submit(self.check_in,)
72
+ executor.submit(Audits.run_audit_query, f'MSCK REPAIR TABLE {table}', None,)
73
+ executor.submit(Audits.put_meta, Audits.PARTITIONS_ADDED, meta,)
78
74
 
79
75
  def help(self, _: ReplState):
80
- return f"{AuditRepairTables.COMMAND} \t run MSCK REPAIR command for new partition discovery"
81
-
82
- def check_in(self):
83
- payload = {
84
- 'action': 'check-in'
85
- }
86
- audit_endpoint = Config().get("audit.endpoint", "https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/")
87
- try:
88
- response = requests.post(audit_endpoint, json=payload, timeout=Config().get("audit.timeout", 10))
89
- if response.status_code in [200, 201]:
90
- Config().debug(response.text)
91
- else:
92
- log2(f"Error: {response.status_code} {response.text}")
93
- except requests.exceptions.Timeout as e:
94
- log2(f"Timeout occurred: {e}")
76
+ return f"{AuditRepairTables.COMMAND} \t run MSCK REPAIR command for new partition discovery"
@@ -0,0 +1,57 @@
1
+ import concurrent
2
+
3
+ from adam.commands.command import Command
4
+ from adam.config import Config
5
+ from adam.repl_state import ReplState
6
+ from adam.utils import log2
7
+ from adam.utils_audits import AuditMeta, Audits
8
+
9
+ class AuditRun(Command):
10
+ COMMAND = 'audit run'
11
+
12
+ # the singleton pattern
13
+ def __new__(cls, *args, **kwargs):
14
+ if not hasattr(cls, 'instance'): cls.instance = super(AuditRun, cls).__new__(cls)
15
+
16
+ return cls.instance
17
+
18
+ def __init__(self, successor: Command=None):
19
+ super().__init__(successor)
20
+ self.auto_repaired = False
21
+
22
+ def command(self):
23
+ return AuditRun.COMMAND
24
+
25
+ def required(self):
26
+ return ReplState.L
27
+
28
+ def run(self, cmd: str, state: ReplState):
29
+ if not(args := self.args(cmd)):
30
+ return super().run(cmd, state)
31
+
32
+ state, args = self.apply_state(args, state)
33
+ if not self.validate_state(state):
34
+ return state
35
+
36
+ meta: AuditMeta = Audits.get_meta()
37
+ clusters = Audits.find_new_clusters(meta.cluster_last_checked)
38
+ Audits.put_meta(Audits.ADD_CLUSTERS, meta, clusters=clusters)
39
+ if clusters:
40
+ log2(f'Added {len(clusters)} new clusters.')
41
+ tables = Config().get('audit.athena.repair-cluster-tables', 'cluster').split(',')
42
+ with concurrent.futures.ThreadPoolExecutor(max_workers=Config().get('audit.workers', 3)) as executor:
43
+ for table in tables:
44
+ executor.submit(Audits.run_audit_query, f'MSCK REPAIR TABLE {table}', None,)
45
+ else:
46
+ log2(f'No new clusters were found.')
47
+
48
+ return state
49
+
50
+ def completion(self, state: ReplState):
51
+ if state.device == ReplState.L:
52
+ return super().completion(state)
53
+
54
+ return {}
55
+
56
+ def help(self, _: ReplState):
57
+ return f"{AuditRun.COMMAND} \t run"
@@ -0,0 +1,50 @@
1
+ from adam.commands.audit.utils_show_top10 import extract_limit_and_duration
2
+ from adam.commands.command import Command
3
+ from adam.repl_state import ReplState
4
+ from adam.utils import log2
5
+ from adam.utils_audits import Audits
6
+
7
+ class ShowLast10(Command):
8
+ COMMAND = 'show last'
9
+
10
+ # the singleton pattern
11
+ def __new__(cls, *args, **kwargs):
12
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowLast10, cls).__new__(cls)
13
+
14
+ return cls.instance
15
+
16
+ def __init__(self, successor: Command=None):
17
+ super().__init__(successor)
18
+
19
+ def command(self):
20
+ return ShowLast10.COMMAND
21
+
22
+ def required(self):
23
+ return ReplState.L
24
+
25
+ def run(self, cmd: str, state: ReplState):
26
+ if not(args := self.args(cmd)):
27
+ return super().run(cmd, state)
28
+
29
+ state, args = self.apply_state(args, state)
30
+ if not self.validate_state(state):
31
+ return state
32
+
33
+ limit, date_condition = extract_limit_and_duration(args)
34
+
35
+ query = '\n '.join([
36
+ "SELECT * FROM audit",
37
+ f"WHERE drive <> 'z' and ({date_condition})",
38
+ f"ORDER BY ts DESC LIMIT {limit};"])
39
+ log2(query)
40
+ log2()
41
+ Audits.run_audit_query(query)
42
+
43
+ return state
44
+
45
+ def completion(self, _: ReplState):
46
+
47
+ return {}
48
+
49
+ def help(self, _: ReplState):
50
+ return f'{ShowLast10.COMMAND} [limit]\t show last <limit> audit lines'
@@ -0,0 +1,49 @@
1
+ from adam.commands.audit.utils_show_top10 import extract_limit_and_duration
2
+ from adam.commands.command import Command
3
+ from adam.repl_state import ReplState
4
+ from adam.utils import log2
5
+ from adam.utils_audits import Audits
6
+
7
+ class ShowSlow10(Command):
8
+ COMMAND = 'show slow'
9
+
10
+ # the singleton pattern
11
+ def __new__(cls, *args, **kwargs):
12
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowSlow10, cls).__new__(cls)
13
+
14
+ return cls.instance
15
+
16
+ def __init__(self, successor: Command=None):
17
+ super().__init__(successor)
18
+
19
+ def command(self):
20
+ return ShowSlow10.COMMAND
21
+
22
+ def required(self):
23
+ return ReplState.L
24
+
25
+ def run(self, cmd: str, state: ReplState):
26
+ if not(args := self.args(cmd)):
27
+ return super().run(cmd, state)
28
+
29
+ state, args = self.apply_state(args, state)
30
+ if not self.validate_state(state):
31
+ return state
32
+
33
+ limit, date_condition = extract_limit_and_duration(args)
34
+
35
+ query = '\n '.join([
36
+ "SELECT * FROM audit",
37
+ f"WHERE drive <> 'z' and ({date_condition})",
38
+ f"ORDER BY CAST(duration AS REAL) DESC LIMIT {limit};"])
39
+ log2(query)
40
+ log2()
41
+ Audits.run_audit_query(query)
42
+
43
+ return state
44
+
45
+ def completion(self, _: ReplState):
46
+ return {}
47
+
48
+ def help(self, _: ReplState):
49
+ return f'{ShowSlow10.COMMAND} [limit]\t show slow <limit> audit lines'
@@ -0,0 +1,48 @@
1
+ from adam.commands.audit.utils_show_top10 import extract_limit_and_duration
2
+ from adam.commands.command import Command
3
+ from adam.repl_state import ReplState
4
+ from adam.utils import log2
5
+ from adam.utils_audits import Audits
6
+
7
+ class ShowTop10(Command):
8
+ COMMAND = 'show top'
9
+
10
+ # the singleton pattern
11
+ def __new__(cls, *args, **kwargs):
12
+ if not hasattr(cls, 'instance'): cls.instance = super(ShowTop10, cls).__new__(cls)
13
+
14
+ return cls.instance
15
+
16
+ def __init__(self, successor: Command=None):
17
+ super().__init__(successor)
18
+
19
+ def command(self):
20
+ return ShowTop10.COMMAND
21
+
22
+ def required(self):
23
+ return ReplState.L
24
+
25
+ def run(self, cmd: str, state: ReplState):
26
+ if not(args := self.args(cmd)):
27
+ return super().run(cmd, state)
28
+
29
+ state, args = self.apply_state(args, state)
30
+ if not self.validate_state(state):
31
+ return state
32
+
33
+ limit, date_condition = extract_limit_and_duration(args)
34
+ query = '\n '.join([
35
+ "SELECT min(c) AS cluster, line, COUNT(*) AS cnt, avg(CAST(duration AS REAL)) AS duration",
36
+ f"FROM audit WHERE drive <> 'z' and ({date_condition})",
37
+ f"GROUP BY line ORDER BY cnt DESC LIMIT {limit};"])
38
+ log2(query)
39
+ log2()
40
+ Audits.run_audit_query(query)
41
+
42
+ return state
43
+
44
+ def completion(self, _: ReplState):
45
+ return {}
46
+
47
+ def help(self, _: ReplState):
48
+ return f'{ShowTop10.COMMAND} [limit]\t show top <limit> audit lines'
@@ -0,0 +1,59 @@
1
+ from datetime import datetime, timedelta
2
+
3
+ from adam.utils_audits import Audits
4
+ from adam.utils_repl.automata_completer import AutomataCompleter
5
+ from adam.utils_repl.state_machine import StateMachine
6
+
7
+ def extract_limit_and_duration(args: list[str]) -> tuple[int, datetime]:
8
+ limit = 10
9
+ _from = datetime.now() - timedelta(days=30)
10
+ if args:
11
+ try:
12
+ limit = int(args[0])
13
+ except:
14
+ pass
15
+
16
+ if len(args) > 2 and args[1] == 'over':
17
+ if args[2] == 'day':
18
+ _from = datetime.now() - timedelta(days=1)
19
+
20
+ return (limit, Audits.date_from(_from))
21
+
22
+ def limit_and_duration_completion():
23
+ return {'10': {'over': {
24
+ 'day': None,
25
+ 'month': None
26
+ }}}
27
+
28
+ SHOW_TOP10_SPEC = [
29
+ ' > show > show',
30
+ 'show > last|slow|top > show_top ^ last,slow,top',
31
+ 'show_top > word > show_top_n ^ 10',
32
+ 'show_top_n > over > show_top_n_over ^ over',
33
+ 'show_top_n_over > day|month > show_top_n_over$ ^ day,month',
34
+ ]
35
+
36
+ SHOW_TOP10_KEYWORDS = [
37
+ 'show',
38
+ 'top',
39
+ 'last',
40
+ 'slow',
41
+ 'over',
42
+ 'day',
43
+ 'month'
44
+ ]
45
+
46
+ class ShowTop10StateMachine(StateMachine[str]):
47
+ def spec(self) -> str:
48
+ return SHOW_TOP10_SPEC
49
+
50
+ def keywords(self) -> list[str]:
51
+ return SHOW_TOP10_KEYWORDS
52
+
53
+ def show_top10_completions_for_nesting():
54
+ return {
55
+ 'show': {
56
+ 'last': AutomataCompleter(ShowTop10StateMachine(), first_term='show last'),
57
+ 'slow': AutomataCompleter(ShowTop10StateMachine(), first_term='show slow'),
58
+ 'top': AutomataCompleter(ShowTop10StateMachine(), first_term='show top'),
59
+ }}
@@ -0,0 +1,150 @@
1
+ from adam.commands.command import Command
2
+ from adam.utils_k8s.cassandra_clusters import CassandraClusters
3
+ from adam.utils_k8s.cassandra_nodes import CassandraNodes
4
+ from adam.pod_exec_result import PodExecResult
5
+ from adam.repl_state import BashSession, ReplState, RequiredState
6
+ from adam.utils_repl.automata_completer import AutomataCompleter
7
+ from adam.utils_repl.state_machine import StateMachine
8
+
9
+ class Bash(Command):
10
+ COMMAND = 'bash'
11
+
12
+ # the singleton pattern
13
+ def __new__(cls, *args, **kwargs):
14
+ if not hasattr(cls, 'instance'): cls.instance = super(Bash, cls).__new__(cls)
15
+
16
+ return cls.instance
17
+
18
+ def __init__(self, successor: Command=None):
19
+ super().__init__(successor)
20
+
21
+ def command(self):
22
+ return Bash.COMMAND
23
+
24
+ def required(self):
25
+ return RequiredState.CLUSTER_OR_POD
26
+
27
+ def run(self, cmd: str, s0: ReplState):
28
+ if not(args := self.args(cmd)):
29
+ return super().run(cmd, s0)
30
+
31
+ state, args = self.apply_state(args, s0, args_to_check=2)
32
+ if not self.validate_state(state):
33
+ return state
34
+
35
+ if state.in_repl:
36
+ if s0.sts != state.sts or s0.pod != state.pod:
37
+ r = self.exec_with_dir(state, args)
38
+ else:
39
+ r = self.exec_with_dir(s0, args)
40
+
41
+ if not r:
42
+ state.exit_bash()
43
+
44
+ return 'inconsistent pwd'
45
+
46
+ return r
47
+ else:
48
+ command = ' '.join(args)
49
+
50
+ if state.pod:
51
+ CassandraNodes.exec(state.pod, state.namespace, command, show_out=True)
52
+ elif state.sts:
53
+ CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=True)
54
+
55
+ return state
56
+
57
+ def exec_with_dir(self, state: ReplState, args: list[str]) -> list[PodExecResult]:
58
+ session_just_created = False
59
+ if not args:
60
+ session_just_created = True
61
+ session = BashSession(state.device)
62
+ state.enter_bash(session)
63
+
64
+ if state.bash_session:
65
+ if args != ['pwd']:
66
+ if args:
67
+ args.append('&&')
68
+ args.extend(['pwd', '>', f'/tmp/.qing-{state.bash_session.session_id}'])
69
+
70
+ if not session_just_created:
71
+ if pwd := state.bash_session.pwd(state):
72
+ args = ['cd', pwd, '&&'] + args
73
+
74
+ command = ' '.join(args)
75
+
76
+ rs = []
77
+
78
+ if state.pod:
79
+ rs = [CassandraNodes.exec(state.pod, state.namespace, command,
80
+ show_out=not session_just_created, shell='bash')]
81
+ elif state.sts:
82
+ rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash',
83
+ show_out=not session_just_created, shell='bash')
84
+
85
+ return rs
86
+
87
+ def completion(self, state: ReplState):
88
+ if state.pod or state.sts:
89
+ return {Bash.COMMAND: AutomataCompleter(BashStateMachine())}
90
+
91
+ return {}
92
+
93
+ def help(self, _: ReplState):
94
+ return f'{Bash.COMMAND} [bash-commands]\t run bash on the Cassandra nodes'
95
+
96
+ BASH_SPEC = [
97
+ # <command> ::= <simple_command> | <pipeline> | <conditional_command>
98
+ # <simple_command> ::= <word> <argument>* <redirection>*
99
+ # <pipeline> ::= <command> '|' <command>
100
+ # <conditional_command> ::= <command> '&&' <command> | <command> '||' <command>
101
+ # <word> ::= <letter> <letter_or_digit>*
102
+ # <argument> ::= <word>
103
+ # <redirection> ::= '>' <filename> | '<' <filename>
104
+ # <filename> ::= <word>
105
+ # <letter> ::= 'a' | 'b' | ... | 'z' | 'A' | 'B' | ... | 'Z'
106
+ # <digit> ::= '0' | '1' | ... | '9'
107
+ # <letter_or_digit> ::= <letter> | <digit>
108
+
109
+ ' > word > word',
110
+ 'word > word > word ^ |,>,2>,<,&,&&,||',
111
+ '- > pipe > word_pipe',
112
+ '- > _rdr0_ > word_rdr0',
113
+ '- > _rdr1_ > word_rdr1',
114
+ '- > _rdr2_ > word_rdr2',
115
+ '- > & > word_bg ^ |,>,2>,<,&,&&,||',
116
+ '- > &&|_or_ > word',
117
+ 'word_a > word > word',
118
+ 'word_pipe > word > word',
119
+ 'word_rdr0 > word > word_rdr0_f',
120
+ 'word_rdr1 > word > word_rdr1_f',
121
+ 'word_rdr2 > word > word_rdr2_f',
122
+ 'word_rdr1_f > pipe > word_pipe ^ |,2>,<,&,&&,||',
123
+ '- > _rdr2_ > word_rdr2',
124
+ '- > _rdr0_ > word_rdr0',
125
+ 'word_rdr2_f > pipe > word_pipe ^ |,<,&,&&,||',
126
+ '- > _rdr0_ > word_rdr0',
127
+ '- > & > word_bg ^ |,>,2>,<,&,&&,||',
128
+ '- > &&|_or_ > word',
129
+ 'word_rdr0_f > pipe > word_pipe ^ |,&,&&,||',
130
+ '- > & > word_bg ^ |,>,2>,<,&,&&,||',
131
+ '- > &&|_or_ > word',
132
+ 'word_bg > &&|_or_ > ^ &&,||',
133
+ ]
134
+
135
+ BASH_KEYWORDS = [
136
+ '&',
137
+ '&&',
138
+ '|',
139
+ '||',
140
+ '>',
141
+ '2>',
142
+ '>>',
143
+ '<'
144
+ ]
145
+ class BashStateMachine(StateMachine[str]):
146
+ def spec(self) -> str:
147
+ return BASH_SPEC
148
+
149
+ def keywords(self) -> list[str]:
150
+ return BASH_KEYWORDS