snowflake-cli 3.2.2__py3-none-any.whl → 3.4.1__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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/__main__.py +2 -2
- snowflake/cli/_app/cli_app.py +224 -192
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
- snowflake/cli/_app/constants.py +4 -0
- snowflake/cli/_app/snow_connector.py +12 -0
- snowflake/cli/_app/telemetry.py +10 -3
- snowflake/cli/_plugins/connection/util.py +12 -19
- snowflake/cli/_plugins/cortex/commands.py +2 -4
- snowflake/cli/_plugins/git/manager.py +1 -1
- snowflake/cli/_plugins/helpers/commands.py +207 -1
- snowflake/cli/_plugins/nativeapp/artifacts.py +16 -628
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +42 -20
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +9 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -3
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +44 -34
- snowflake/cli/_plugins/nativeapp/commands.py +113 -21
- snowflake/cli/_plugins/nativeapp/constants.py +5 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +226 -296
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +911 -141
- snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
- snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
- snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +246 -0
- snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_directive/commands.py +243 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
- snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +1184 -80
- snowflake/cli/_plugins/nativeapp/utils.py +11 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +7 -3
- snowflake/cli/_plugins/nativeapp/version/commands.py +32 -5
- snowflake/cli/_plugins/notebook/commands.py +55 -2
- snowflake/cli/_plugins/notebook/exceptions.py +1 -1
- snowflake/cli/_plugins/notebook/manager.py +7 -5
- snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
- snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
- snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
- snowflake/cli/_plugins/notebook/types.py +3 -0
- snowflake/cli/_plugins/snowpark/commands.py +48 -30
- snowflake/cli/_plugins/snowpark/common.py +47 -2
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +247 -4
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
- snowflake/cli/_plugins/snowpark/zipper.py +33 -1
- snowflake/cli/_plugins/spcs/common.py +129 -0
- snowflake/cli/_plugins/spcs/services/commands.py +131 -14
- snowflake/cli/_plugins/spcs/services/manager.py +169 -1
- snowflake/cli/_plugins/stage/commands.py +2 -1
- snowflake/cli/_plugins/stage/diff.py +60 -39
- snowflake/cli/_plugins/stage/manager.py +34 -13
- snowflake/cli/_plugins/stage/utils.py +1 -1
- snowflake/cli/_plugins/streamlit/commands.py +10 -1
- snowflake/cli/_plugins/streamlit/manager.py +70 -22
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +131 -1
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
- snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
- snowflake/cli/_plugins/workspace/commands.py +6 -5
- snowflake/cli/_plugins/workspace/manager.py +9 -5
- snowflake/cli/api/artifacts/__init__.py +13 -0
- snowflake/cli/api/artifacts/bundle_map.py +500 -0
- snowflake/cli/api/artifacts/common.py +78 -0
- snowflake/cli/api/artifacts/utils.py +82 -0
- snowflake/cli/api/cli_global_context.py +36 -2
- snowflake/cli/api/commands/flags.py +10 -4
- snowflake/cli/api/commands/utils.py +28 -2
- snowflake/cli/api/config.py +6 -2
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/constants.py +10 -1
- snowflake/cli/api/entities/common.py +81 -14
- snowflake/cli/api/entities/resolver.py +160 -0
- snowflake/cli/api/entities/utils.py +65 -23
- snowflake/cli/api/errno.py +63 -3
- snowflake/cli/api/feature_flags.py +19 -4
- snowflake/cli/api/metrics.py +21 -27
- snowflake/cli/api/project/definition_conversion.py +4 -4
- snowflake/cli/api/project/project_paths.py +28 -0
- snowflake/cli/api/project/schemas/entities/common.py +130 -1
- snowflake/cli/api/project/schemas/entities/entities.py +4 -0
- snowflake/cli/api/project/schemas/project_definition.py +54 -6
- snowflake/cli/api/project/schemas/updatable_model.py +2 -2
- snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
- snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
- snowflake/cli/api/project/util.py +45 -0
- snowflake/cli/api/secure_path.py +6 -0
- snowflake/cli/api/sql_execution.py +5 -1
- snowflake/cli/api/stage_path.py +7 -2
- snowflake/cli/api/utils/graph.py +3 -0
- snowflake/cli/api/utils/path_utils.py +24 -0
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +14 -15
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +96 -82
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +1 -1
- snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import json
|
|
17
18
|
import sys
|
|
19
|
+
from datetime import datetime
|
|
18
20
|
from typing import TextIO
|
|
19
21
|
|
|
20
22
|
from click import ClickException
|
|
@@ -23,6 +25,22 @@ from snowflake.cli.api.exceptions import ObjectAlreadyExistsError
|
|
|
23
25
|
from snowflake.cli.api.project.util import unquote_identifier
|
|
24
26
|
from snowflake.connector.errors import ProgrammingError
|
|
25
27
|
|
|
28
|
+
EVENT_COLUMN_NAMES = [
|
|
29
|
+
"TIMESTAMP",
|
|
30
|
+
"START_TIMESTAMP",
|
|
31
|
+
"OBSERVED_TIMESTAMP",
|
|
32
|
+
"TRACE",
|
|
33
|
+
"RESOURCE",
|
|
34
|
+
"RESOURCE_ATTRIBUTES",
|
|
35
|
+
"SCOPE",
|
|
36
|
+
"SCOPE_ATTRIBUTES",
|
|
37
|
+
"RECORD_TYPE",
|
|
38
|
+
"RECORD",
|
|
39
|
+
"RECORD_ATTRIBUTES",
|
|
40
|
+
"VALUE",
|
|
41
|
+
"EXEMPLARS",
|
|
42
|
+
]
|
|
43
|
+
|
|
26
44
|
if not sys.stdout.closed and sys.stdout.isatty():
|
|
27
45
|
GREEN = "\033[32m"
|
|
28
46
|
BLUE = "\033[34m"
|
|
@@ -124,5 +142,116 @@ def new_logs_only(prev_log_records: list[str], new_log_records: list[str]) -> li
|
|
|
124
142
|
return new_log_records_sorted
|
|
125
143
|
|
|
126
144
|
|
|
145
|
+
def build_resource_clause(
|
|
146
|
+
service_name: str, instance_id: str, container_name: str
|
|
147
|
+
) -> str:
|
|
148
|
+
resource_filters = []
|
|
149
|
+
if service_name:
|
|
150
|
+
resource_filters.append(
|
|
151
|
+
f"resource_attributes:\"snow.service.name\" = '{service_name}'"
|
|
152
|
+
)
|
|
153
|
+
if instance_id:
|
|
154
|
+
resource_filters.append(
|
|
155
|
+
f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' "
|
|
156
|
+
f"OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')"
|
|
157
|
+
)
|
|
158
|
+
if container_name:
|
|
159
|
+
resource_filters.append(
|
|
160
|
+
f"resource_attributes:\"snow.service.container.name\" = '{container_name}'"
|
|
161
|
+
)
|
|
162
|
+
return " and ".join(resource_filters) if resource_filters else "1=1"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build_time_clauses(
|
|
166
|
+
since: str | datetime | None, until: str | datetime | None
|
|
167
|
+
) -> tuple[str, str]:
|
|
168
|
+
since_clause = ""
|
|
169
|
+
until_clause = ""
|
|
170
|
+
|
|
171
|
+
if isinstance(since, datetime):
|
|
172
|
+
since_clause = f"and timestamp >= '{since}'"
|
|
173
|
+
elif isinstance(since, str) and since:
|
|
174
|
+
since_clause = f"and timestamp >= sysdate() - interval '{since}'"
|
|
175
|
+
|
|
176
|
+
if isinstance(until, datetime):
|
|
177
|
+
until_clause = f"and timestamp <= '{until}'"
|
|
178
|
+
elif isinstance(until, str) and until:
|
|
179
|
+
until_clause = f"and timestamp <= sysdate() - interval '{until}'"
|
|
180
|
+
|
|
181
|
+
return since_clause, until_clause
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def format_event_row(event_dict: dict) -> dict:
|
|
185
|
+
try:
|
|
186
|
+
resource_attributes = json.loads(event_dict.get("RESOURCE_ATTRIBUTES", "{}"))
|
|
187
|
+
record_attributes = json.loads(event_dict.get("RECORD_ATTRIBUTES", "{}"))
|
|
188
|
+
record = json.loads(event_dict.get("RECORD", "{}"))
|
|
189
|
+
|
|
190
|
+
database_name = resource_attributes.get("snow.database.name", "N/A")
|
|
191
|
+
schema_name = resource_attributes.get("snow.schema.name", "N/A")
|
|
192
|
+
service_name = resource_attributes.get("snow.service.name", "N/A")
|
|
193
|
+
instance_name = resource_attributes.get("snow.service.instance", "N/A")
|
|
194
|
+
container_name = resource_attributes.get("snow.service.container.name", "N/A")
|
|
195
|
+
event_name = record_attributes.get("event.name", "Unknown Event")
|
|
196
|
+
event_value = event_dict.get("VALUE", "Unknown Value")
|
|
197
|
+
severity = record.get("severity_text", "Unknown Severity")
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"TIMESTAMP": event_dict.get("TIMESTAMP", "N/A"),
|
|
201
|
+
"DATABASE NAME": database_name,
|
|
202
|
+
"SCHEMA NAME": schema_name,
|
|
203
|
+
"SERVICE NAME": service_name,
|
|
204
|
+
"INSTANCE ID": instance_name,
|
|
205
|
+
"CONTAINER NAME": container_name,
|
|
206
|
+
"SEVERITY": severity,
|
|
207
|
+
"EVENT NAME": event_name,
|
|
208
|
+
"EVENT VALUE": event_value,
|
|
209
|
+
}
|
|
210
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
211
|
+
raise RecordProcessingError(f"Error processing event row.")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def format_metric_row(metric_dict: dict) -> dict:
|
|
215
|
+
try:
|
|
216
|
+
resource_attributes = json.loads(metric_dict["RESOURCE_ATTRIBUTES"])
|
|
217
|
+
record = json.loads(metric_dict["RECORD"])
|
|
218
|
+
|
|
219
|
+
database_name = resource_attributes.get("snow.database.name", "N/A")
|
|
220
|
+
schema_name = resource_attributes.get("snow.schema.name", "N/A")
|
|
221
|
+
service_name = resource_attributes.get("snow.service.name", "N/A")
|
|
222
|
+
instance_name = resource_attributes.get(
|
|
223
|
+
"snow.service.container.instance", "N/A"
|
|
224
|
+
)
|
|
225
|
+
container_name = resource_attributes.get("snow.service.container.name", "N/A")
|
|
226
|
+
|
|
227
|
+
metric_name = record["metric"].get("name", "Unknown Metric")
|
|
228
|
+
metric_value = metric_dict.get("VALUE", "Unknown Value")
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"TIMESTAMP": metric_dict.get("TIMESTAMP", "N/A"),
|
|
232
|
+
"DATABASE NAME": database_name,
|
|
233
|
+
"SCHEMA NAME": schema_name,
|
|
234
|
+
"SERVICE NAME": service_name,
|
|
235
|
+
"INSTANCE ID": instance_name,
|
|
236
|
+
"CONTAINER NAME": container_name,
|
|
237
|
+
"METRIC NAME": metric_name,
|
|
238
|
+
"METRIC VALUE": metric_value,
|
|
239
|
+
}
|
|
240
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
241
|
+
raise RecordProcessingError(f"Error processing metric row.")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class RecordProcessingError(ClickException):
|
|
245
|
+
"""Raised when processing an event or metric record fails due to invalid data."""
|
|
246
|
+
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class SPCSEventTableError(ClickException):
|
|
251
|
+
"""Raised when there is an issue related to the SPCS event table."""
|
|
252
|
+
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
127
256
|
class NoPropertiesProvidedError(ClickException):
|
|
128
257
|
pass
|
|
@@ -37,9 +37,12 @@ from snowflake.cli.api.commands.flags import (
|
|
|
37
37
|
)
|
|
38
38
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
39
39
|
from snowflake.cli.api.constants import ObjectType
|
|
40
|
-
from snowflake.cli.api.exceptions import
|
|
40
|
+
from snowflake.cli.api.exceptions import (
|
|
41
|
+
IncompatibleParametersError,
|
|
42
|
+
)
|
|
41
43
|
from snowflake.cli.api.identifiers import FQN
|
|
42
44
|
from snowflake.cli.api.output.types import (
|
|
45
|
+
CollectionResult,
|
|
43
46
|
CommandResult,
|
|
44
47
|
MessageResult,
|
|
45
48
|
QueryJsonValueResult,
|
|
@@ -55,6 +58,38 @@ app = SnowTyperFactory(
|
|
|
55
58
|
short_help="Manages services.",
|
|
56
59
|
)
|
|
57
60
|
|
|
61
|
+
# Define common options
|
|
62
|
+
container_name_option = typer.Option(
|
|
63
|
+
...,
|
|
64
|
+
"--container-name",
|
|
65
|
+
help="Name of the container.",
|
|
66
|
+
show_default=False,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
instance_id_option = typer.Option(
|
|
70
|
+
...,
|
|
71
|
+
"--instance-id",
|
|
72
|
+
help="ID of the service instance, starting with 0.",
|
|
73
|
+
show_default=False,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
since_option = typer.Option(
|
|
77
|
+
default="",
|
|
78
|
+
help="Fetch events that are newer than this time ago, in Snowflake interval syntax.",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
until_option = typer.Option(
|
|
82
|
+
default="",
|
|
83
|
+
help="Fetch events that are older than this time ago, in Snowflake interval syntax.",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
show_all_columns_option = typer.Option(
|
|
87
|
+
False,
|
|
88
|
+
"--all",
|
|
89
|
+
is_flag=True,
|
|
90
|
+
help="Fetch all columns.",
|
|
91
|
+
)
|
|
92
|
+
|
|
58
93
|
|
|
59
94
|
def _service_name_callback(name: FQN) -> FQN:
|
|
60
95
|
if not is_valid_object_name(name.identifier, max_depth=2, allow_quoted=False):
|
|
@@ -209,18 +244,8 @@ def status(name: FQN = ServiceNameArgument, **options) -> CommandResult:
|
|
|
209
244
|
@app.command(requires_connection=True)
|
|
210
245
|
def logs(
|
|
211
246
|
name: FQN = ServiceNameArgument,
|
|
212
|
-
container_name: str =
|
|
213
|
-
|
|
214
|
-
"--container-name",
|
|
215
|
-
help="Name of the container.",
|
|
216
|
-
show_default=False,
|
|
217
|
-
),
|
|
218
|
-
instance_id: str = typer.Option(
|
|
219
|
-
...,
|
|
220
|
-
"--instance-id",
|
|
221
|
-
help="ID of the service instance, starting with 0.",
|
|
222
|
-
show_default=False,
|
|
223
|
-
),
|
|
247
|
+
container_name: str = container_name_option,
|
|
248
|
+
instance_id: str = instance_id_option,
|
|
224
249
|
num_lines: int = typer.Option(
|
|
225
250
|
DEFAULT_NUM_LINES, "--num-lines", help="Number of lines to retrieve."
|
|
226
251
|
),
|
|
@@ -237,12 +262,17 @@ def logs(
|
|
|
237
262
|
False, "--include-timestamps", help="Include timestamps in logs.", is_flag=True
|
|
238
263
|
),
|
|
239
264
|
follow: bool = typer.Option(
|
|
240
|
-
False,
|
|
265
|
+
False,
|
|
266
|
+
"--follow",
|
|
267
|
+
help="Stream logs in real-time.",
|
|
268
|
+
is_flag=True,
|
|
269
|
+
hidden=True,
|
|
241
270
|
),
|
|
242
271
|
follow_interval: int = typer.Option(
|
|
243
272
|
2,
|
|
244
273
|
"--follow-interval",
|
|
245
274
|
help="Set custom polling intervals for log streaming (--follow flag) in seconds.",
|
|
275
|
+
hidden=True,
|
|
246
276
|
),
|
|
247
277
|
**options,
|
|
248
278
|
):
|
|
@@ -288,6 +318,93 @@ def logs(
|
|
|
288
318
|
return StreamResult(cast(Generator[CommandResult, None, None], stream))
|
|
289
319
|
|
|
290
320
|
|
|
321
|
+
@app.command(
|
|
322
|
+
requires_connection=True,
|
|
323
|
+
)
|
|
324
|
+
def events(
|
|
325
|
+
name: FQN = ServiceNameArgument,
|
|
326
|
+
container_name: str = container_name_option,
|
|
327
|
+
instance_id: str = instance_id_option,
|
|
328
|
+
since: str = since_option,
|
|
329
|
+
until: str = until_option,
|
|
330
|
+
first: int = typer.Option(
|
|
331
|
+
default=None,
|
|
332
|
+
show_default=False,
|
|
333
|
+
help="Fetch only the first N events. Cannot be used with --last.",
|
|
334
|
+
),
|
|
335
|
+
last: int = typer.Option(
|
|
336
|
+
default=None,
|
|
337
|
+
show_default=False,
|
|
338
|
+
help="Fetch only the last N events. Cannot be used with --first.",
|
|
339
|
+
),
|
|
340
|
+
show_all_columns: bool = show_all_columns_option,
|
|
341
|
+
**options,
|
|
342
|
+
):
|
|
343
|
+
"""
|
|
344
|
+
Retrieve platform events for a service container.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
if first is not None and last is not None:
|
|
348
|
+
raise IncompatibleParametersError(["--first", "--last"])
|
|
349
|
+
|
|
350
|
+
manager = ServiceManager()
|
|
351
|
+
events = manager.get_events(
|
|
352
|
+
service_name=name.identifier,
|
|
353
|
+
container_name=container_name,
|
|
354
|
+
instance_id=instance_id,
|
|
355
|
+
since=since,
|
|
356
|
+
until=until,
|
|
357
|
+
first=first,
|
|
358
|
+
last=last,
|
|
359
|
+
show_all_columns=show_all_columns,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if not events:
|
|
363
|
+
return MessageResult("No events found.")
|
|
364
|
+
|
|
365
|
+
return CollectionResult(events)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@app.command(
|
|
369
|
+
requires_connection=True,
|
|
370
|
+
)
|
|
371
|
+
def metrics(
|
|
372
|
+
name: FQN = ServiceNameArgument,
|
|
373
|
+
container_name: str = container_name_option,
|
|
374
|
+
instance_id: str = instance_id_option,
|
|
375
|
+
since: str = since_option,
|
|
376
|
+
until: str = until_option,
|
|
377
|
+
show_all_columns: bool = show_all_columns_option,
|
|
378
|
+
**options,
|
|
379
|
+
):
|
|
380
|
+
"""
|
|
381
|
+
Retrieve platform metrics for a service container.
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
manager = ServiceManager()
|
|
385
|
+
if since or until:
|
|
386
|
+
metrics = manager.get_all_metrics(
|
|
387
|
+
service_name=name.identifier,
|
|
388
|
+
container_name=container_name,
|
|
389
|
+
instance_id=instance_id,
|
|
390
|
+
since=since,
|
|
391
|
+
until=until,
|
|
392
|
+
show_all_columns=show_all_columns,
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
metrics = manager.get_latest_metrics(
|
|
396
|
+
service_name=name.identifier,
|
|
397
|
+
container_name=container_name,
|
|
398
|
+
instance_id=instance_id,
|
|
399
|
+
show_all_columns=show_all_columns,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if not metrics:
|
|
403
|
+
return MessageResult("No metrics found.")
|
|
404
|
+
|
|
405
|
+
return CollectionResult(metrics)
|
|
406
|
+
|
|
407
|
+
|
|
291
408
|
@app.command(requires_connection=True)
|
|
292
409
|
def upgrade(
|
|
293
410
|
name: FQN = ServiceNameArgument,
|
|
@@ -16,14 +16,21 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import time
|
|
19
|
+
from datetime import datetime
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import List, Optional
|
|
21
22
|
|
|
22
23
|
import yaml
|
|
23
24
|
from snowflake.cli._plugins.object.common import Tag
|
|
24
25
|
from snowflake.cli._plugins.spcs.common import (
|
|
26
|
+
EVENT_COLUMN_NAMES,
|
|
25
27
|
NoPropertiesProvidedError,
|
|
28
|
+
SPCSEventTableError,
|
|
29
|
+
build_resource_clause,
|
|
30
|
+
build_time_clauses,
|
|
26
31
|
filter_log_timestamp,
|
|
32
|
+
format_event_row,
|
|
33
|
+
format_metric_row,
|
|
27
34
|
handle_object_already_exists,
|
|
28
35
|
new_logs_only,
|
|
29
36
|
strip_empty_lines,
|
|
@@ -31,7 +38,7 @@ from snowflake.cli._plugins.spcs.common import (
|
|
|
31
38
|
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB, ObjectType
|
|
32
39
|
from snowflake.cli.api.secure_path import SecurePath
|
|
33
40
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
34
|
-
from snowflake.connector.cursor import SnowflakeCursor
|
|
41
|
+
from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
35
42
|
from snowflake.connector.errors import ProgrammingError
|
|
36
43
|
|
|
37
44
|
|
|
@@ -199,6 +206,167 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
199
206
|
except KeyboardInterrupt:
|
|
200
207
|
return
|
|
201
208
|
|
|
209
|
+
def get_account_event_table(self):
|
|
210
|
+
query = "show parameters like 'event_table' in account"
|
|
211
|
+
results = self.execute_query(query, cursor_class=DictCursor)
|
|
212
|
+
event_table = next(
|
|
213
|
+
(r["value"] for r in results if r["key"] == "EVENT_TABLE"), ""
|
|
214
|
+
)
|
|
215
|
+
if not event_table:
|
|
216
|
+
raise SPCSEventTableError("No SPCS event table configured in the account.")
|
|
217
|
+
return event_table
|
|
218
|
+
|
|
219
|
+
def get_events(
|
|
220
|
+
self,
|
|
221
|
+
service_name: str,
|
|
222
|
+
instance_id: str,
|
|
223
|
+
container_name: str,
|
|
224
|
+
since: str | datetime | None = None,
|
|
225
|
+
until: str | datetime | None = None,
|
|
226
|
+
first: Optional[int] = None,
|
|
227
|
+
last: Optional[int] = None,
|
|
228
|
+
show_all_columns: bool = False,
|
|
229
|
+
):
|
|
230
|
+
|
|
231
|
+
account_event_table = self.get_account_event_table()
|
|
232
|
+
resource_clause = build_resource_clause(
|
|
233
|
+
service_name, instance_id, container_name
|
|
234
|
+
)
|
|
235
|
+
since_clause, until_clause = build_time_clauses(since, until)
|
|
236
|
+
|
|
237
|
+
first_clause = f"limit {first}" if first is not None else ""
|
|
238
|
+
last_clause = f"limit {last}" if last is not None else ""
|
|
239
|
+
|
|
240
|
+
query = f"""\
|
|
241
|
+
select *
|
|
242
|
+
from (
|
|
243
|
+
select *
|
|
244
|
+
from {account_event_table}
|
|
245
|
+
where (
|
|
246
|
+
{resource_clause}
|
|
247
|
+
{since_clause}
|
|
248
|
+
{until_clause}
|
|
249
|
+
)
|
|
250
|
+
and record_type = 'LOG'
|
|
251
|
+
and scope['name'] = 'snow.spcs.platform'
|
|
252
|
+
order by timestamp desc
|
|
253
|
+
{last_clause}
|
|
254
|
+
)
|
|
255
|
+
order by timestamp asc
|
|
256
|
+
{first_clause}
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
cursor = self.execute_query(query)
|
|
260
|
+
raw_events = cursor.fetchall()
|
|
261
|
+
if not raw_events:
|
|
262
|
+
return []
|
|
263
|
+
|
|
264
|
+
if show_all_columns:
|
|
265
|
+
return [dict(zip(EVENT_COLUMN_NAMES, event)) for event in raw_events]
|
|
266
|
+
|
|
267
|
+
formatted_events = []
|
|
268
|
+
for raw_event in raw_events:
|
|
269
|
+
event_dict = dict(zip(EVENT_COLUMN_NAMES, raw_event))
|
|
270
|
+
formatted = format_event_row(event_dict)
|
|
271
|
+
formatted_events.append(formatted)
|
|
272
|
+
|
|
273
|
+
return formatted_events
|
|
274
|
+
|
|
275
|
+
def get_all_metrics(
|
|
276
|
+
self,
|
|
277
|
+
service_name: str,
|
|
278
|
+
instance_id: str,
|
|
279
|
+
container_name: str,
|
|
280
|
+
since: str | datetime | None = None,
|
|
281
|
+
until: str | datetime | None = None,
|
|
282
|
+
show_all_columns: bool = False,
|
|
283
|
+
):
|
|
284
|
+
|
|
285
|
+
account_event_table = self.get_account_event_table()
|
|
286
|
+
resource_clause = build_resource_clause(
|
|
287
|
+
service_name, instance_id, container_name
|
|
288
|
+
)
|
|
289
|
+
since_clause, until_clause = build_time_clauses(since, until)
|
|
290
|
+
|
|
291
|
+
query = f"""\
|
|
292
|
+
select *
|
|
293
|
+
from {account_event_table}
|
|
294
|
+
where (
|
|
295
|
+
{resource_clause}
|
|
296
|
+
{since_clause}
|
|
297
|
+
{until_clause}
|
|
298
|
+
)
|
|
299
|
+
and record_type = 'METRIC'
|
|
300
|
+
and scope['name'] = 'snow.spcs.platform'
|
|
301
|
+
order by timestamp desc
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
cursor = self.execute_query(query)
|
|
305
|
+
raw_metrics = cursor.fetchall()
|
|
306
|
+
if not raw_metrics:
|
|
307
|
+
return []
|
|
308
|
+
|
|
309
|
+
if show_all_columns:
|
|
310
|
+
return [dict(zip(EVENT_COLUMN_NAMES, metric)) for metric in raw_metrics]
|
|
311
|
+
|
|
312
|
+
formatted_metrics = []
|
|
313
|
+
for raw_metric in raw_metrics:
|
|
314
|
+
metric_dict = dict(zip(EVENT_COLUMN_NAMES, raw_metric))
|
|
315
|
+
formatted = format_metric_row(metric_dict)
|
|
316
|
+
formatted_metrics.append(formatted)
|
|
317
|
+
|
|
318
|
+
return formatted_metrics
|
|
319
|
+
|
|
320
|
+
def get_latest_metrics(
|
|
321
|
+
self,
|
|
322
|
+
service_name: str,
|
|
323
|
+
instance_id: str,
|
|
324
|
+
container_name: str,
|
|
325
|
+
show_all_columns: bool = False,
|
|
326
|
+
):
|
|
327
|
+
|
|
328
|
+
account_event_table = self.get_account_event_table()
|
|
329
|
+
resource_clause = build_resource_clause(
|
|
330
|
+
service_name, instance_id, container_name
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
query = f"""
|
|
334
|
+
with rankedmetrics as (
|
|
335
|
+
select
|
|
336
|
+
*,
|
|
337
|
+
row_number() over (
|
|
338
|
+
partition by record['metric']['name']
|
|
339
|
+
order by timestamp desc
|
|
340
|
+
) as rank
|
|
341
|
+
from {account_event_table}
|
|
342
|
+
where
|
|
343
|
+
record_type = 'METRIC'
|
|
344
|
+
and scope['name'] = 'snow.spcs.platform'
|
|
345
|
+
and {resource_clause}
|
|
346
|
+
and timestamp > dateadd('hour', -1, current_timestamp)
|
|
347
|
+
)
|
|
348
|
+
select *
|
|
349
|
+
from rankedmetrics
|
|
350
|
+
where rank = 1
|
|
351
|
+
order by timestamp desc;
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
cursor = self.execute_query(query)
|
|
355
|
+
raw_metrics = cursor.fetchall()
|
|
356
|
+
if not raw_metrics:
|
|
357
|
+
return []
|
|
358
|
+
|
|
359
|
+
if show_all_columns:
|
|
360
|
+
return [dict(zip(EVENT_COLUMN_NAMES, metric)) for metric in raw_metrics]
|
|
361
|
+
|
|
362
|
+
formatted_metrics = []
|
|
363
|
+
for raw_metric in raw_metrics:
|
|
364
|
+
metric_dict = dict(zip(EVENT_COLUMN_NAMES, raw_metric))
|
|
365
|
+
formatted = format_metric_row(metric_dict)
|
|
366
|
+
formatted_metrics.append(formatted)
|
|
367
|
+
|
|
368
|
+
return formatted_metrics
|
|
369
|
+
|
|
202
370
|
def upgrade_spec(self, service_name: str, spec_path: Path):
|
|
203
371
|
spec = self._read_yaml(spec_path)
|
|
204
372
|
query = f"alter service {service_name} from specification $$ {spec} $$"
|
|
@@ -192,7 +192,8 @@ def stage_diff(
|
|
|
192
192
|
Diffs a stage with a local folder.
|
|
193
193
|
"""
|
|
194
194
|
diff: DiffResult = compute_stage_diff(
|
|
195
|
-
local_root=Path(folder_name),
|
|
195
|
+
local_root=Path(folder_name),
|
|
196
|
+
stage_path=StageManager.stage_path_parts_from_str(stage_name), # noqa: SLF001
|
|
196
197
|
)
|
|
197
198
|
if get_cli_context().output_format == OutputFormat.JSON:
|
|
198
199
|
return ObjectResult(diff.to_dict())
|