snowglobe-cli 0.1.0__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.
- snowglobe/__init__.py +6 -0
- snowglobe/__main__.py +3 -0
- snowglobe/cli/__init__.py +0 -0
- snowglobe/cli/access.py +197 -0
- snowglobe/cli/app.py +148 -0
- snowglobe/cli/context.py +48 -0
- snowglobe/cli/cost.py +291 -0
- snowglobe/cli/debug.py +265 -0
- snowglobe/cli/diff.py +34 -0
- snowglobe/cli/optimizer.py +91 -0
- snowglobe/cli/prompts.py +161 -0
- snowglobe/cli/report.py +91 -0
- snowglobe/cli/shell.py +1437 -0
- snowglobe/cli/shell_completer.py +128 -0
- snowglobe/collectors/access.py +882 -0
- snowglobe/collectors/query_history.py +46 -0
- snowglobe/collectors/query_profile.py +101 -0
- snowglobe/config/loader.py +42 -0
- snowglobe/core/access_service.py +721 -0
- snowglobe/core/cost_service.py +929 -0
- snowglobe/core/optimizer.py +92 -0
- snowglobe/core/query_service.py +48 -0
- snowglobe/core/report_service.py +110 -0
- snowglobe/core/risk_service.py +358 -0
- snowglobe/engines/access/__init__.py +0 -0
- snowglobe/engines/access/explainer.py +113 -0
- snowglobe/engines/access/resolver.py +199 -0
- snowglobe/engines/ai/cortex_optimizer.py +69 -0
- snowglobe/engines/optimizer/query_optimizer.py +326 -0
- snowglobe/graphs/__init__.py +0 -0
- snowglobe/graphs/role_graph.py +140 -0
- snowglobe/graphs/user_graph.py +64 -0
- snowglobe/models/__init__.py +0 -0
- snowglobe/models/access.py +65 -0
- snowglobe/models/access_path.py +15 -0
- snowglobe/models/object_ref.py +11 -0
- snowglobe/models/object_type.py +50 -0
- snowglobe/models/optimizer.py +15 -0
- snowglobe/models/privilege.py +78 -0
- snowglobe/models/query.py +59 -0
- snowglobe/output/__init__.py +0 -0
- snowglobe/output/cli.py +413 -0
- snowglobe/queries/__init__.py +0 -0
- snowglobe/queries/query_history.py +37 -0
- snowglobe/snowflake/connection.py +75 -0
- snowglobe/state/db.py +559 -0
- snowglobe/state/state.py +60 -0
- snowglobe/templates/report.md.j2 +55 -0
- snowglobe/tests/access_tests.py +5 -0
- snowglobe/tui/__init__.py +1 -0
- snowglobe/tui/__main__.py +3 -0
- snowglobe/tui/app.py +299 -0
- snowglobe/tui/screens/__init__.py +0 -0
- snowglobe/tui/screens/access.py +627 -0
- snowglobe/tui/screens/cost.py +831 -0
- snowglobe/tui/screens/home.py +222 -0
- snowglobe/tui/screens/refresh.py +222 -0
- snowglobe/tui/screens/reports.py +252 -0
- snowglobe/tui/screens/risk.py +417 -0
- snowglobe/tui/screens/tune.py +254 -0
- snowglobe/tui/widgets/__init__.py +0 -0
- snowglobe/tui/widgets/access_paths.py +63 -0
- snowglobe/tui/widgets/cache_badge.py +28 -0
- snowglobe/tui/widgets/header.py +21 -0
- snowglobe/tui/widgets/nav.py +32 -0
- snowglobe_cli-0.1.0.dist-info/METADATA +368 -0
- snowglobe_cli-0.1.0.dist-info/RECORD +71 -0
- snowglobe_cli-0.1.0.dist-info/WHEEL +5 -0
- snowglobe_cli-0.1.0.dist-info/entry_points.txt +2 -0
- snowglobe_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
- snowglobe_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
2
|
+
|
|
3
|
+
SHELL_COMMANDS = ["check", "roles", "members", "path", "escalation", "scan", "use", "set", "access", "whoaccess", "create", "cost", "optimize", "drift", "unused", "report", "refresh", "status", "debug", "help", "exit", "?"]
|
|
4
|
+
COST_SUBCOMMANDS = ["summary", "warehouses", "users", "ai", "ai-users", "services", "queries", "trend", "storage", "budget", "replication", "mv"]
|
|
5
|
+
SET_FIELDS = ["object_type", "object_name", "privilege"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SnowglobeCompleter(Completer):
|
|
9
|
+
def __init__(self, ctx):
|
|
10
|
+
self.ctx = ctx
|
|
11
|
+
|
|
12
|
+
def get_completions(self, document, complete_event):
|
|
13
|
+
text = document.text_before_cursor
|
|
14
|
+
word = document.get_word_before_cursor()
|
|
15
|
+
start_position = -len(word)
|
|
16
|
+
|
|
17
|
+
parts = text.strip().split()
|
|
18
|
+
new_word = text.endswith(" ")
|
|
19
|
+
|
|
20
|
+
# No input yet — suggest all commands
|
|
21
|
+
if not parts:
|
|
22
|
+
for cmd in SHELL_COMMANDS:
|
|
23
|
+
yield Completion(cmd, start_position=start_position)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
command = parts[0]
|
|
27
|
+
|
|
28
|
+
# Completing command name (first word, still typing)
|
|
29
|
+
if len(parts) == 1 and not new_word:
|
|
30
|
+
for cmd in SHELL_COMMANDS:
|
|
31
|
+
if cmd.startswith(word):
|
|
32
|
+
yield Completion(cmd, start_position=start_position)
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# ---- roles command (expects a username) ----
|
|
36
|
+
if command == "roles":
|
|
37
|
+
if (len(parts) == 1 and new_word) or (len(parts) == 2 and not new_word):
|
|
38
|
+
yield from self._complete_users(start_position, word if not new_word else None)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# ---- members command (expects a role) ----
|
|
42
|
+
if command == "members":
|
|
43
|
+
if (len(parts) == 1 and new_word) or (len(parts) == 2 and not new_word):
|
|
44
|
+
yield from self._complete_roles(start_position, word if not new_word else None)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# ---- path command (expects from_role, then to_role) ----
|
|
48
|
+
if command == "path":
|
|
49
|
+
if (len(parts) == 1 and new_word) or (len(parts) == 2 and not new_word):
|
|
50
|
+
yield from self._complete_roles(start_position, word if not new_word else None)
|
|
51
|
+
return
|
|
52
|
+
if (len(parts) == 2 and new_word) or (len(parts) == 3 and not new_word):
|
|
53
|
+
yield from self._complete_roles(start_position, word if not new_word else None)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# ---- escalation command (expects a role) ----
|
|
57
|
+
if command == "escalation":
|
|
58
|
+
if (len(parts) == 1 and new_word) or (len(parts) == 2 and not new_word):
|
|
59
|
+
yield from self._complete_roles(start_position, word if not new_word else None)
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# ---- cost command (expects a subcommand) ----
|
|
63
|
+
if command == "cost":
|
|
64
|
+
if (len(parts) == 1 and new_word) or (len(parts) == 2 and not new_word):
|
|
65
|
+
prefix = word if not new_word else None
|
|
66
|
+
for sub in COST_SUBCOMMANDS:
|
|
67
|
+
if prefix is None or sub.startswith(prefix):
|
|
68
|
+
yield Completion(sub, start_position=start_position)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# ---- use command ----
|
|
72
|
+
if command == "use":
|
|
73
|
+
if len(parts) == 1 and new_word:
|
|
74
|
+
for option in ["user", "role"]:
|
|
75
|
+
yield Completion(option, start_position=0)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
if len(parts) == 2 and not new_word:
|
|
79
|
+
for option in ["user", "role"]:
|
|
80
|
+
if option.startswith(word):
|
|
81
|
+
yield Completion(option, start_position=start_position)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Completing the name (third token)
|
|
85
|
+
if len(parts) == 2 and new_word:
|
|
86
|
+
kind = parts[1]
|
|
87
|
+
if kind == "role":
|
|
88
|
+
yield from self._complete_roles(0, None)
|
|
89
|
+
elif kind == "user":
|
|
90
|
+
yield from self._complete_users(0, None)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if len(parts) == 3 and not new_word:
|
|
94
|
+
kind = parts[1]
|
|
95
|
+
if kind == "role":
|
|
96
|
+
yield from self._complete_roles(start_position, word)
|
|
97
|
+
elif kind == "user":
|
|
98
|
+
yield from self._complete_users(start_position, word)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# ---- set command ----
|
|
102
|
+
if command == "set":
|
|
103
|
+
if len(parts) == 1 and new_word:
|
|
104
|
+
for field in SET_FIELDS:
|
|
105
|
+
yield Completion(field, start_position=0)
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
if len(parts) == 2 and not new_word:
|
|
109
|
+
for field in SET_FIELDS:
|
|
110
|
+
if field.startswith(word):
|
|
111
|
+
yield Completion(field, start_position=start_position)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
def _complete_roles(self, start_position, prefix=None):
|
|
115
|
+
"""Yield role name completions."""
|
|
116
|
+
if not self.ctx.role_graph:
|
|
117
|
+
return
|
|
118
|
+
for role in self.ctx.role_graph.roles.keys():
|
|
119
|
+
if prefix is None or role.upper().startswith(prefix.upper()):
|
|
120
|
+
yield Completion(role, start_position=start_position)
|
|
121
|
+
|
|
122
|
+
def _complete_users(self, start_position, prefix=None):
|
|
123
|
+
"""Yield user name completions."""
|
|
124
|
+
if not self.ctx.user_graph:
|
|
125
|
+
return
|
|
126
|
+
for user in self.ctx.user_graph.assigned_roles.keys():
|
|
127
|
+
if prefix is None or user.upper().startswith(prefix.upper()):
|
|
128
|
+
yield Completion(user, start_position=start_position)
|