snowglobe-cli 0.1.0__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.
- snowglobe_cli-0.1.0/LICENSE +202 -0
- snowglobe_cli-0.1.0/PKG-INFO +368 -0
- snowglobe_cli-0.1.0/README.md +341 -0
- snowglobe_cli-0.1.0/pyproject.toml +48 -0
- snowglobe_cli-0.1.0/setup.cfg +4 -0
- snowglobe_cli-0.1.0/snowglobe/__init__.py +6 -0
- snowglobe_cli-0.1.0/snowglobe/__main__.py +3 -0
- snowglobe_cli-0.1.0/snowglobe/cli/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/cli/access.py +197 -0
- snowglobe_cli-0.1.0/snowglobe/cli/app.py +148 -0
- snowglobe_cli-0.1.0/snowglobe/cli/context.py +48 -0
- snowglobe_cli-0.1.0/snowglobe/cli/cost.py +291 -0
- snowglobe_cli-0.1.0/snowglobe/cli/debug.py +265 -0
- snowglobe_cli-0.1.0/snowglobe/cli/diff.py +34 -0
- snowglobe_cli-0.1.0/snowglobe/cli/optimizer.py +91 -0
- snowglobe_cli-0.1.0/snowglobe/cli/prompts.py +161 -0
- snowglobe_cli-0.1.0/snowglobe/cli/report.py +91 -0
- snowglobe_cli-0.1.0/snowglobe/cli/shell.py +1437 -0
- snowglobe_cli-0.1.0/snowglobe/cli/shell_completer.py +128 -0
- snowglobe_cli-0.1.0/snowglobe/collectors/access.py +882 -0
- snowglobe_cli-0.1.0/snowglobe/collectors/query_history.py +46 -0
- snowglobe_cli-0.1.0/snowglobe/collectors/query_profile.py +101 -0
- snowglobe_cli-0.1.0/snowglobe/config/loader.py +42 -0
- snowglobe_cli-0.1.0/snowglobe/core/access_service.py +721 -0
- snowglobe_cli-0.1.0/snowglobe/core/cost_service.py +929 -0
- snowglobe_cli-0.1.0/snowglobe/core/optimizer.py +92 -0
- snowglobe_cli-0.1.0/snowglobe/core/query_service.py +48 -0
- snowglobe_cli-0.1.0/snowglobe/core/report_service.py +110 -0
- snowglobe_cli-0.1.0/snowglobe/core/risk_service.py +358 -0
- snowglobe_cli-0.1.0/snowglobe/engines/access/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/engines/access/explainer.py +113 -0
- snowglobe_cli-0.1.0/snowglobe/engines/access/resolver.py +199 -0
- snowglobe_cli-0.1.0/snowglobe/engines/ai/cortex_optimizer.py +69 -0
- snowglobe_cli-0.1.0/snowglobe/engines/optimizer/query_optimizer.py +326 -0
- snowglobe_cli-0.1.0/snowglobe/graphs/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/graphs/role_graph.py +140 -0
- snowglobe_cli-0.1.0/snowglobe/graphs/user_graph.py +64 -0
- snowglobe_cli-0.1.0/snowglobe/models/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/models/access.py +65 -0
- snowglobe_cli-0.1.0/snowglobe/models/access_path.py +15 -0
- snowglobe_cli-0.1.0/snowglobe/models/object_ref.py +11 -0
- snowglobe_cli-0.1.0/snowglobe/models/object_type.py +50 -0
- snowglobe_cli-0.1.0/snowglobe/models/optimizer.py +15 -0
- snowglobe_cli-0.1.0/snowglobe/models/privilege.py +78 -0
- snowglobe_cli-0.1.0/snowglobe/models/query.py +59 -0
- snowglobe_cli-0.1.0/snowglobe/output/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/output/cli.py +413 -0
- snowglobe_cli-0.1.0/snowglobe/queries/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/queries/query_history.py +37 -0
- snowglobe_cli-0.1.0/snowglobe/snowflake/connection.py +75 -0
- snowglobe_cli-0.1.0/snowglobe/state/db.py +559 -0
- snowglobe_cli-0.1.0/snowglobe/state/state.py +60 -0
- snowglobe_cli-0.1.0/snowglobe/templates/report.md.j2 +55 -0
- snowglobe_cli-0.1.0/snowglobe/tests/access_tests.py +5 -0
- snowglobe_cli-0.1.0/snowglobe/tui/__init__.py +1 -0
- snowglobe_cli-0.1.0/snowglobe/tui/__main__.py +3 -0
- snowglobe_cli-0.1.0/snowglobe/tui/app.py +299 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/access.py +627 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/cost.py +831 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/home.py +222 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/refresh.py +222 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/reports.py +252 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/risk.py +417 -0
- snowglobe_cli-0.1.0/snowglobe/tui/screens/tune.py +254 -0
- snowglobe_cli-0.1.0/snowglobe/tui/widgets/__init__.py +0 -0
- snowglobe_cli-0.1.0/snowglobe/tui/widgets/access_paths.py +63 -0
- snowglobe_cli-0.1.0/snowglobe/tui/widgets/cache_badge.py +28 -0
- snowglobe_cli-0.1.0/snowglobe/tui/widgets/header.py +21 -0
- snowglobe_cli-0.1.0/snowglobe/tui/widgets/nav.py +32 -0
- snowglobe_cli-0.1.0/snowglobe_cli.egg-info/PKG-INFO +368 -0
- snowglobe_cli-0.1.0/snowglobe_cli.egg-info/SOURCES.txt +80 -0
- snowglobe_cli-0.1.0/snowglobe_cli.egg-info/dependency_links.txt +1 -0
- snowglobe_cli-0.1.0/snowglobe_cli.egg-info/entry_points.txt +2 -0
- snowglobe_cli-0.1.0/snowglobe_cli.egg-info/requires.txt +18 -0
- snowglobe_cli-0.1.0/snowglobe_cli.egg-info/top_level.txt +1 -0
- snowglobe_cli-0.1.0/tests/test_access.py +122 -0
- snowglobe_cli-0.1.0/tests/test_optimizer_engine.py +259 -0
- snowglobe_cli-0.1.0/tests/test_output.py +94 -0
- snowglobe_cli-0.1.0/tests/test_query_validation.py +32 -0
- snowglobe_cli-0.1.0/tests/test_state_db.py +115 -0
- snowglobe_cli-0.1.0/tests/test_tui_tune.py +203 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Copyright 2025 Jaryd Thornton
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snowglobe-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Explainable cost and access visibility for Snowflake
|
|
5
|
+
Author-email: Jaryd Thornton <jaryd90@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: cryptography==45.0.5
|
|
11
|
+
Requires-Dist: Jinja2==3.1.6
|
|
12
|
+
Requires-Dist: pandas==2.3.1
|
|
13
|
+
Requires-Dist: prompt_toolkit>=3.0.0
|
|
14
|
+
Requires-Dist: pydantic==2.11.7
|
|
15
|
+
Requires-Dist: pyOpenSSL==25.1.0
|
|
16
|
+
Requires-Dist: PyYAML==6.0.2
|
|
17
|
+
Requires-Dist: rich==14.0.0
|
|
18
|
+
Requires-Dist: snowflake_connector_python==3.16.0
|
|
19
|
+
Requires-Dist: sqlglot==27.1.0
|
|
20
|
+
Requires-Dist: typer==0.16.0
|
|
21
|
+
Requires-Dist: typing_extensions==4.14.1
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Provides-Extra: tui
|
|
25
|
+
Requires-Dist: textual>=0.80; extra == "tui"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img src="assets/logo.svg" alt="Snowglobe" width="600"/>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<a href="https://pypi.org/project/snowglobe-cli/"><img src="https://img.shields.io/pypi/v/snowglobe-cli" alt="PyPI version"/></a>
|
|
34
|
+
<a href="https://pypi.org/project/snowglobe-cli/"><img src="https://img.shields.io/pypi/pyversions/snowglobe-cli" alt="Python versions"/></a>
|
|
35
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"/></a>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<strong>Explainable cost and access visibility for Snowflake — read-only by design.</strong>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
Snowglobe helps analytics, data platform, and security teams answer three questions about their Snowflake account:
|
|
43
|
+
|
|
44
|
+
1. **Where did our Snowflake spend come from?**
|
|
45
|
+
2. **Who owns and can access what?**
|
|
46
|
+
3. **Who is responsible when cost or access looks wrong?**
|
|
47
|
+
|
|
48
|
+
It observes, explains, and recommends — and it never writes to your account.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Table of contents
|
|
53
|
+
|
|
54
|
+
- [Three ways to use it](#three-ways-to-use-it)
|
|
55
|
+
- [Installation](#installation)
|
|
56
|
+
- [Requirements](#requirements)
|
|
57
|
+
- [Configuration](#configuration)
|
|
58
|
+
- [Quickstart](#quickstart)
|
|
59
|
+
- [Features](#features)
|
|
60
|
+
- [The TUI](#the-tui)
|
|
61
|
+
- [The interactive shell](#the-interactive-shell)
|
|
62
|
+
- [Headless CLI](#headless-cli)
|
|
63
|
+
- [Security](#security)
|
|
64
|
+
- [Limitations](#limitations)
|
|
65
|
+
- [Contributing](#contributing)
|
|
66
|
+
- [License](#license)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Three ways to use it
|
|
71
|
+
|
|
72
|
+
| Interface | Command | When to use it |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| **TUI** *(default)* | `snowglobe` | Day-to-day exploration. Seven screens, mouse + keyboard, optional vim navigation, theme switcher. |
|
|
75
|
+
| **Interactive shell** | `snowglobe shell` | Wizards and quick lookups in a REPL. Useful when you only want one or two checks. |
|
|
76
|
+
| **Headless CLI** | `snowglobe <subcommand>` | CI, cron jobs, scripts, piping into other tools. Outputs `table`, `json`, or `csv` per command. |
|
|
77
|
+
|
|
78
|
+
All three share the same local SQLite cache and the same service layer, so anything you do in one is reflected in the others.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install 'snowglobe-cli[tui]' # includes the Textual TUI (recommended)
|
|
86
|
+
pip install snowglobe-cli # CLI + shell only, no Textual dependency
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or from source:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/jcolethornton/snowglobe.git
|
|
93
|
+
cd snowglobe
|
|
94
|
+
pip install -e '.[tui]'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- Python **3.12+**
|
|
102
|
+
- A Snowflake account
|
|
103
|
+
- A Snowflake role with `IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE` (grants read access to `ACCOUNT_USAGE`)
|
|
104
|
+
- Optionally: `IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE` for `ORGANIZATION_USAGE` to get your contracted storage rate
|
|
105
|
+
|
|
106
|
+
**Grant access if needed:**
|
|
107
|
+
```sql
|
|
108
|
+
GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE TO ROLE <your_role>;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
Snowglobe loads connection profiles from `~/.snowglobe/config.yaml`. Multiple profiles are supported and selected with `--profile <name>`.
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
# ~/.snowglobe/config.yaml
|
|
119
|
+
|
|
120
|
+
default:
|
|
121
|
+
account: "abc123.us-east-1"
|
|
122
|
+
user: "jdoe@example.com"
|
|
123
|
+
role: "ANALYST"
|
|
124
|
+
warehouse: "ANALYTICS_WH"
|
|
125
|
+
|
|
126
|
+
# Auth — choose one:
|
|
127
|
+
password: "hunter2" # password auth
|
|
128
|
+
# private_key_path: "~/.ssh/snowflake.p8" # key-pair auth
|
|
129
|
+
# private_key_pwd: "passphrase" # key passphrase (if encrypted)
|
|
130
|
+
|
|
131
|
+
# Optional settings:
|
|
132
|
+
vim: true # enable vim navigation in the TUI
|
|
133
|
+
cortex_model: "claude-haiku-4-5" # Cortex AI model for query optimiser
|
|
134
|
+
|
|
135
|
+
prod:
|
|
136
|
+
account: "abc123.us-east-1"
|
|
137
|
+
user: "admin_user"
|
|
138
|
+
password: "${SNOWFLAKE_PROD_PASSWORD}" # environment variables are expanded
|
|
139
|
+
role: "SYSADMIN"
|
|
140
|
+
warehouse: "ETL_WH"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Quickstart
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# 1. Populate the local SQLite cache from Snowflake
|
|
149
|
+
snowglobe refresh
|
|
150
|
+
|
|
151
|
+
# 2. Launch the TUI (the default)
|
|
152
|
+
snowglobe
|
|
153
|
+
|
|
154
|
+
# Or explicitly, with vim-style navigation
|
|
155
|
+
snowglobe tui --vim
|
|
156
|
+
|
|
157
|
+
# Drop into the REPL shell instead
|
|
158
|
+
snowglobe shell
|
|
159
|
+
|
|
160
|
+
# Headless commands
|
|
161
|
+
snowglobe access check --user jdoe --object-type TABLE \
|
|
162
|
+
--object-name MY_DB.PUBLIC.ORDERS --privilege SELECT
|
|
163
|
+
snowglobe cost summary --days 30
|
|
164
|
+
snowglobe optimize query --query-id <query_id>
|
|
165
|
+
snowglobe report full --days 30 --output report.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Features
|
|
171
|
+
|
|
172
|
+
### Access explainability
|
|
173
|
+
|
|
174
|
+
- **Why does X have access?** — Trace every role-inheritance path that grants a user or role a privilege on an object, not just a flat list of grants.
|
|
175
|
+
- **Who can access this?** — Reverse lookup an object: every role and user with access, grouped by privilege, including paths through inherited roles.
|
|
176
|
+
- **Where can this role create things?** — CREATE privilege visibility across account / database / schema scopes with the granting roles called out.
|
|
177
|
+
- **What roles does a user have?** — Direct, excluded, and inherited roles plus the total effective set.
|
|
178
|
+
- **Who has a specific role?** — Direct and inherited members.
|
|
179
|
+
- **Does role X inherit from role Y?** — Every inheritance path between two roles.
|
|
180
|
+
- **What's changed since last refresh?** — Drift detection across grants, role edges, and user assignments.
|
|
181
|
+
|
|
182
|
+
### Risk & privilege escalation
|
|
183
|
+
|
|
184
|
+
- **Escalation scan** — Walk every role in the account, find paths to admin roles (ACCOUNTADMIN, SYSADMIN, SECURITYADMIN, USERADMIN, or any role with `MANAGE GRANTS` / DB OWNERSHIP / `IMPORTED PRIVILEGES ON SNOWFLAKE`), and score each path with a composite risk score (target weight × inverse hops × log of user count).
|
|
185
|
+
- **Single-role escalation check** — Pick one role and see exactly which admin targets it can reach and through which chain.
|
|
186
|
+
- **Dormant escalation risks** — Cross-references inactive users (no successful login in 90 days) against risk-bearing roles.
|
|
187
|
+
- **Direct privilege risks** — Roles with dangerous account-level grants that bypass the role graph entirely.
|
|
188
|
+
- **Unused privileges** — Roles with data grants but no recent `QUERY_HISTORY` activity, surfaced for cleanup.
|
|
189
|
+
|
|
190
|
+
### Cost & query attribution
|
|
191
|
+
|
|
192
|
+
- **12 cost views** — Account summary, daily trend with rolling average, top expensive queries, per-warehouse, per-user (warehouse + Cortex AI), AI services (Cortex Functions / Analyst / Agent / Code / Snowflake Intelligence), services (pipes / tasks / SPCS / auto-clustering / search optimisation), per-DB storage with monthly cost estimate, replication, materialized-view refresh costs, and Snowflake-native budget status.
|
|
193
|
+
- **Real query attribution** — Uses `QUERY_ATTRIBUTION_HISTORY.CREDITS_ATTRIBUTED_COMPUTE` for per-query credit cost where available; falls back gracefully to `QUERY_HISTORY` with a clear note.
|
|
194
|
+
- **Cortex AI cost tracking** — Each Cortex view is queried individually so accounts without certain features still get partial data rather than a crash.
|
|
195
|
+
- **1-hour TTL local cache** — Cost views are cached in SQLite so repeated visits within the hour are instant; `Re-fetch` forces a fresh Snowflake call.
|
|
196
|
+
- **Drill-downs** — Click a warehouse for its daily trend; click a user for their per-warehouse credit breakdown; click a top query to load it into the Tune screen.
|
|
197
|
+
|
|
198
|
+
### Query optimiser
|
|
199
|
+
|
|
200
|
+
- **Eight heuristic detectors** over `GET_QUERY_OPERATOR_STATS` — join explosion, disk spill, pruning failure, cartesian joins, large scans, large window functions, large aggregations, heavy network shuffle.
|
|
201
|
+
- **Snowflake-native insights** from `QUERY_INSIGHTS` (where available).
|
|
202
|
+
- **Operator tree** with per-operator score and time percentage.
|
|
203
|
+
- **Cortex AI suggestions** — opt-in `AI_COMPLETE` call that takes the SQL + heuristic findings + cost attribution and produces a narrative optimisation recommendation. Model is configurable per profile (`cortex_model` in config); defaults to `claude-haiku-4-5`.
|
|
204
|
+
|
|
205
|
+
### Reports
|
|
206
|
+
|
|
207
|
+
- **Full report** — cost summary + AI costs + storage + top queries, rendered as markdown via Jinja templates.
|
|
208
|
+
- **Cost-only report** — same shape minus the query section.
|
|
209
|
+
- **User access report** — every effective role and grant for a user, formatted as a markdown audit trail.
|
|
210
|
+
- **CSV exports** — most cost commands accept `--csv <path>` to write directly to a file; the risk scan supports `--csv` and `--json`.
|
|
211
|
+
|
|
212
|
+
### Local SQLite cache
|
|
213
|
+
|
|
214
|
+
- All grants, role edges, and user assignments cached locally for instant lookups.
|
|
215
|
+
- **Incremental refresh** — uses `MODIFIED_ON` / `DELETED_ON` watermarks to fetch only changed rows.
|
|
216
|
+
- Cost data uses a 1-hour TTL in the same SQLite store.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## The TUI
|
|
221
|
+
|
|
222
|
+
`snowglobe` (or `snowglobe tui`) opens a full-screen Textual app. Persistent header at the top (profile / role / cache age), nav sidebar on the left, footer with active keybindings at the bottom.
|
|
223
|
+
|
|
224
|
+
### Screens
|
|
225
|
+
|
|
226
|
+
Number keys `1`–`7` jump directly:
|
|
227
|
+
|
|
228
|
+
| # | Screen | What's on it |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| **1** | **Home** | KPI cards (cache health, connection, this week's spend + risk count), recent expensive queries, hotkeys (`a` access · `w` who-access · `c` cost · `s` risk · `r` refresh) |
|
|
231
|
+
| **2** | **Access** | Seven tabs: Check / Who-access / Create / Roles / Members / Path / Drift. Object-type aware privilege filtering. |
|
|
232
|
+
| **3** | **Risk** | Five tabs: Scan / Escalation / Dormant / Direct grants / Unused. Re-scan + CSV / JSON export. |
|
|
233
|
+
| **4** | **Cost** | All twelve cost views in one place. Window selector (7d / 30d / 90d), Re-fetch button, drill-downs into per-warehouse / per-user / per-service trends. |
|
|
234
|
+
| **5** | **Tune** | Query optimiser. Three-pane: SQL with syntax highlighting; Heuristics / Native insights / Operator tree / Expensive ops / AI on the right. |
|
|
235
|
+
| **6** | **Reports** | Generate Full / Cost-only / User-access reports with live markdown preview + Save. |
|
|
236
|
+
| **7** | **Refresh** | State counts, refresh actions, connection diagnostics, streaming log. |
|
|
237
|
+
|
|
238
|
+
### Navigation
|
|
239
|
+
|
|
240
|
+
| Key | Action |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `Tab` / `Shift-Tab` | Cycle focus between widgets |
|
|
243
|
+
| `Enter` | Activate (button press, fire query, open Select dropdown, expand tree node) |
|
|
244
|
+
| `↑` / `↓` | Navigate within lists / tables / dropdowns / trees |
|
|
245
|
+
| `Esc` | Close drill-downs / cancel running workers / blur input (vim mode) |
|
|
246
|
+
| `1`–`7` | Jump to screen by number |
|
|
247
|
+
| `Ctrl-P` | Command palette (switch themes, change profile, etc.) |
|
|
248
|
+
| `q` | Quit |
|
|
249
|
+
|
|
250
|
+
### Vim mode *(optional)*
|
|
251
|
+
|
|
252
|
+
Pass `--vim` or set `vim: true` in your profile config:
|
|
253
|
+
|
|
254
|
+
| Key | Action | Where |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| `j` / `k` | Cursor down / up | Lists, tables, trees |
|
|
257
|
+
| `g` / `G` | Top / bottom | Same |
|
|
258
|
+
| `h` / `l` | Collapse / expand | Tree nodes |
|
|
259
|
+
| `Ctrl-d` / `Ctrl-u` | Half-page down / up | Any scrollable |
|
|
260
|
+
| `Esc` | Blur the focused input | Lets `j`/`k` navigate again after typing |
|
|
261
|
+
|
|
262
|
+
Typing into form fields works as expected — Input widgets consume keypresses before vim bindings fire.
|
|
263
|
+
|
|
264
|
+
### Themes
|
|
265
|
+
|
|
266
|
+
Two branded themes ship with the app: `snowglobe-dark` (default) and `snowglobe-light`. Open the command palette with **`Ctrl-P`**, type `theme`, and pick any — Textual's built-ins (`nord`, `monokai`, `dracula`, `catppuccin-*`, etc.) are also listed.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## The interactive shell
|
|
271
|
+
|
|
272
|
+
`snowglobe shell` drops you into a REPL with fuzzy tab-completion on usernames, roles, and object FQNs.
|
|
273
|
+
|
|
274
|
+
| Command | Description |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `check` | Guided wizard — covers all seven access-style checks step by step |
|
|
277
|
+
| `roles <user>` | Direct + inherited roles for a user |
|
|
278
|
+
| `members <role>` | Direct + inherited users for a role |
|
|
279
|
+
| `path <from> <to>` | Inheritance paths between two roles |
|
|
280
|
+
| `escalation <role>` | Can this role reach admin privileges? |
|
|
281
|
+
| `scan` | Full privilege-escalation scan with risk scoring; `--csv` / `--json` export |
|
|
282
|
+
| `cost` | Cost wizard, or use sub-verbs: `cost summary`, `cost warehouses`, `cost users`, `cost ai`, `cost queries`, `cost trend`, `cost storage`, `cost budget`, `cost replication`, `cost mv` |
|
|
283
|
+
| `optimize <query_id>` | Run the optimiser on a specific query |
|
|
284
|
+
| `drift` | Show access changes since last refresh (or `--days N`) |
|
|
285
|
+
| `unused` | Find roles with data privileges but no recent activity |
|
|
286
|
+
| `report <user>` | Markdown access report for a user |
|
|
287
|
+
| `report full` / `report cost` | Cost / AI / storage / top queries report saved to `.md` |
|
|
288
|
+
| `refresh` | Refresh cached state from Snowflake (`--full` for a complete rebuild) |
|
|
289
|
+
| `status` | Current working state + cache age |
|
|
290
|
+
| `debug` | Connection diagnostics |
|
|
291
|
+
| `help` / `?` | List commands |
|
|
292
|
+
| `use role <name>` / `use user <name>` | Set the active role / user for subsequent commands |
|
|
293
|
+
| `access` / `whoaccess` / `create` | Direct shortcuts that skip the wizard |
|
|
294
|
+
| `exit` | Quit |
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Headless CLI
|
|
299
|
+
|
|
300
|
+
For CI, cron, and scripts. Output formats: `--output table|json` plus per-command `--csv <path>`.
|
|
301
|
+
|
|
302
|
+
| Command | Description |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `snowglobe` | Launch the TUI (falls back to `shell` if Textual isn't installed) |
|
|
305
|
+
| `snowglobe tui [--vim]` | Launch the TUI explicitly |
|
|
306
|
+
| `snowglobe shell` | Launch the REPL shell |
|
|
307
|
+
| `snowglobe refresh [--full]` | Incremental (default) or full state refresh |
|
|
308
|
+
| `snowglobe debug` | Connection diagnostics (eight-step checklist) |
|
|
309
|
+
| `snowglobe access check` | Explain access for a user / role on an object |
|
|
310
|
+
| `snowglobe access create` | Where can a role create objects? |
|
|
311
|
+
| `snowglobe access whoaccess` | Reverse lookup — who can access this object? |
|
|
312
|
+
| `snowglobe cost summary` | Account spend by service type |
|
|
313
|
+
| `snowglobe cost warehouses` | Per-warehouse credit breakdown |
|
|
314
|
+
| `snowglobe cost users` | Complete cost per user (warehouse + Cortex) |
|
|
315
|
+
| `snowglobe cost ai` | AI/ML token costs by service |
|
|
316
|
+
| `snowglobe cost ai-users` | AI costs per user with service split |
|
|
317
|
+
| `snowglobe cost queries` | Top expensive queries by credits or bytes |
|
|
318
|
+
| `snowglobe cost trend` | Daily spend trend with Δ% and rolling 7-day average |
|
|
319
|
+
| `snowglobe cost storage` | Per-DB storage with estimated monthly cost |
|
|
320
|
+
| `snowglobe cost services` | Pipes / serverless tasks / SPCS / clustering / search optimisation |
|
|
321
|
+
| `snowglobe cost budget` | Snowflake-native budget status |
|
|
322
|
+
| `snowglobe cost replication` | Replication credits |
|
|
323
|
+
| `snowglobe cost mv` | Materialized-view refresh credits |
|
|
324
|
+
| `snowglobe optimize query --query-id <id>` | Full query analysis + Cortex AI suggestion |
|
|
325
|
+
| `snowglobe optimize top-queries [--analyze]` | List top expensive queries; optionally analyse each |
|
|
326
|
+
| `snowglobe report full` | Generate the full markdown report |
|
|
327
|
+
| `snowglobe report cost` | Cost-only markdown report |
|
|
328
|
+
| `snowglobe report queries` | Top-queries CSV export |
|
|
329
|
+
|
|
330
|
+
### Global options
|
|
331
|
+
|
|
332
|
+
- `--profile <name>` — connection profile (default: `default`)
|
|
333
|
+
- `--role <name>` — override the role from the profile
|
|
334
|
+
- `--output table|json` — output format
|
|
335
|
+
- `--verbose` / `-v` — verbose output
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Security
|
|
340
|
+
|
|
341
|
+
Snowglobe is **read-only by design** — its guiding principle is that it earns trust by never touching production state.
|
|
342
|
+
|
|
343
|
+
- The Snowflake connection layer actively blocks `CREATE`, `ALTER`, `DROP`, `GRANT`, `REVOKE`, and `TRUNCATE` statements before they reach Snowflake.
|
|
344
|
+
- All metadata is fetched via bulk queries against `SNOWFLAKE.ACCOUNT_USAGE` (and `ORGANIZATION_USAGE.RATE_SHEET_DAILY` if available).
|
|
345
|
+
- All data stays in your environment. No telemetry, no callbacks, no external requests.
|
|
346
|
+
- Credentials are read from the local config file and never logged.
|
|
347
|
+
- Cortex AI calls run inside your Snowflake account via `AI_COMPLETE` — the SQL and operator stats never leave Snowflake.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Limitations
|
|
352
|
+
|
|
353
|
+
- **`ACCOUNT_USAGE` has up to ~45 minutes of latency** — very recent grant or query changes won't appear until the next refresh.
|
|
354
|
+
- **STREAMLIT, NOTEBOOK, DYNAMIC TABLE, ALERT, TAG, SECRET** grants aren't in `GRANTS_TO_ROLES`; Snowglobe falls back to live `SHOW GRANTS ON` for those types (slower, but works).
|
|
355
|
+
- **Query Attribution** (`QUERY_ATTRIBUTION_HISTORY`) requires Snowflake's Query Attribution feature, which is not available on Standard tier or older accounts. Snowglobe falls back to `QUERY_HISTORY` with a note in the UI.
|
|
356
|
+
- **Cortex AI views** are not available in all Snowflake regions or tiers. Snowglobe queries each view independently and silently skips missing ones.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Contributing
|
|
361
|
+
|
|
362
|
+
Pull requests and issues are welcome on [GitHub](https://github.com/jcolethornton/snowglobe).
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
Apache-2.0 © 2026 Jaryd Thornton. See `LICENSE` for the full text.
|