snowflake-cli 3.0.2__py3-none-any.whl → 3.2.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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +3 -0
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
- snowflake/cli/_app/telemetry.py +69 -4
- snowflake/cli/_plugins/connection/commands.py +152 -99
- snowflake/cli/_plugins/connection/util.py +54 -9
- snowflake/cli/_plugins/cortex/manager.py +1 -1
- snowflake/cli/_plugins/git/commands.py +6 -3
- snowflake/cli/_plugins/git/manager.py +9 -4
- snowflake/cli/_plugins/nativeapp/artifacts.py +77 -13
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +5 -3
- snowflake/cli/_plugins/nativeapp/commands.py +144 -188
- snowflake/cli/_plugins/nativeapp/constants.py +1 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +564 -351
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +583 -929
- snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py +58 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +12 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -2
- snowflake/cli/_plugins/nativeapp/sf_facade.py +30 -0
- snowflake/cli/_plugins/nativeapp/sf_facade_constants.py +25 -0
- snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +117 -0
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +525 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/{v2_to_v1_decorator.py → compat.py} +88 -117
- snowflake/cli/_plugins/nativeapp/version/commands.py +36 -32
- snowflake/cli/_plugins/notebook/manager.py +2 -2
- snowflake/cli/_plugins/object/commands.py +10 -1
- snowflake/cli/_plugins/object/manager.py +13 -5
- snowflake/cli/_plugins/snowpark/common.py +63 -21
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -3
- snowflake/cli/_plugins/spcs/common.py +29 -0
- snowflake/cli/_plugins/spcs/compute_pool/manager.py +7 -9
- snowflake/cli/_plugins/spcs/image_registry/manager.py +2 -2
- snowflake/cli/_plugins/spcs/image_repository/commands.py +4 -37
- snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
- snowflake/cli/_plugins/spcs/services/commands.py +100 -17
- snowflake/cli/_plugins/spcs/services/manager.py +108 -16
- snowflake/cli/_plugins/sql/commands.py +9 -1
- snowflake/cli/_plugins/sql/manager.py +9 -4
- snowflake/cli/_plugins/stage/commands.py +28 -19
- snowflake/cli/_plugins/stage/diff.py +17 -17
- snowflake/cli/_plugins/stage/manager.py +304 -84
- snowflake/cli/_plugins/stage/md5.py +1 -1
- snowflake/cli/_plugins/streamlit/manager.py +5 -5
- snowflake/cli/_plugins/workspace/commands.py +27 -4
- snowflake/cli/_plugins/workspace/context.py +38 -0
- snowflake/cli/_plugins/workspace/manager.py +23 -13
- snowflake/cli/api/cli_global_context.py +4 -3
- snowflake/cli/api/commands/flags.py +23 -7
- snowflake/cli/api/config.py +30 -9
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/console/console.py +4 -19
- snowflake/cli/api/entities/common.py +4 -2
- snowflake/cli/api/entities/utils.py +36 -69
- snowflake/cli/api/errno.py +2 -0
- snowflake/cli/api/exceptions.py +41 -0
- snowflake/cli/api/identifiers.py +8 -0
- snowflake/cli/api/metrics.py +223 -7
- snowflake/cli/api/output/types.py +1 -1
- snowflake/cli/api/project/definition_conversion.py +293 -77
- snowflake/cli/api/project/schemas/entities/common.py +11 -0
- snowflake/cli/api/project/schemas/project_definition.py +30 -25
- snowflake/cli/api/rest_api.py +26 -4
- snowflake/cli/api/secure_utils.py +1 -1
- snowflake/cli/api/sql_execution.py +40 -29
- snowflake/cli/api/stage_path.py +244 -0
- snowflake/cli/api/utils/definition_rendering.py +3 -5
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/METADATA +14 -15
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/RECORD +78 -77
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/WHEEL +1 -1
- snowflake/cli/_plugins/nativeapp/manager.py +0 -415
- snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
- snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import time
|
|
4
5
|
from contextlib import contextmanager
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from textwrap import dedent
|
|
8
|
-
from typing import
|
|
9
|
+
from typing import Dict, Generator, List, Literal, Optional, TypedDict
|
|
9
10
|
|
|
10
11
|
import typer
|
|
11
12
|
from click import ClickException, UsageError
|
|
12
13
|
from pydantic import Field, field_validator
|
|
13
|
-
from snowflake.cli._plugins.connection.util import
|
|
14
|
+
from snowflake.cli._plugins.connection.util import (
|
|
15
|
+
UIParameter,
|
|
16
|
+
get_ui_parameter,
|
|
17
|
+
make_snowsight_url,
|
|
18
|
+
)
|
|
19
|
+
from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
20
|
+
find_events_definitions_in_manifest_file,
|
|
21
|
+
)
|
|
14
22
|
from snowflake.cli._plugins.nativeapp.common_flags import (
|
|
15
23
|
ForceOption,
|
|
16
24
|
InteractiveOption,
|
|
@@ -18,6 +26,7 @@ from snowflake.cli._plugins.nativeapp.common_flags import (
|
|
|
18
26
|
)
|
|
19
27
|
from snowflake.cli._plugins.nativeapp.constants import (
|
|
20
28
|
ALLOWED_SPECIAL_COMMENTS,
|
|
29
|
+
AUTHORIZE_TELEMETRY_COL,
|
|
21
30
|
COMMENT_COL,
|
|
22
31
|
NAME_COL,
|
|
23
32
|
OWNER_COL,
|
|
@@ -27,6 +36,9 @@ from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
|
27
36
|
ApplicationPackageEntity,
|
|
28
37
|
ApplicationPackageEntityModel,
|
|
29
38
|
)
|
|
39
|
+
from snowflake.cli._plugins.nativeapp.entities.models.event_sharing_telemetry import (
|
|
40
|
+
EventSharingTelemetry,
|
|
41
|
+
)
|
|
30
42
|
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
31
43
|
ApplicationPackageDoesNotExistError,
|
|
32
44
|
NoEventTableForAccount,
|
|
@@ -40,8 +52,9 @@ from snowflake.cli._plugins.nativeapp.policy import (
|
|
|
40
52
|
from snowflake.cli._plugins.nativeapp.same_account_install_method import (
|
|
41
53
|
SameAccountInstallMethod,
|
|
42
54
|
)
|
|
55
|
+
from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
|
|
43
56
|
from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
|
|
44
|
-
from snowflake.cli._plugins.workspace.
|
|
57
|
+
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
45
58
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
46
59
|
from snowflake.cli.api.console.abc import AbstractConsole
|
|
47
60
|
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
@@ -53,8 +66,12 @@ from snowflake.cli.api.entities.utils import (
|
|
|
53
66
|
)
|
|
54
67
|
from snowflake.cli.api.errno import (
|
|
55
68
|
APPLICATION_NO_LONGER_AVAILABLE,
|
|
69
|
+
APPLICATION_OWNS_EXTERNAL_OBJECTS,
|
|
70
|
+
APPLICATION_REQUIRES_TELEMETRY_SHARING,
|
|
71
|
+
CANNOT_DISABLE_MANDATORY_TELEMETRY,
|
|
56
72
|
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
57
73
|
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
|
|
74
|
+
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
58
75
|
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
59
76
|
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
60
77
|
)
|
|
@@ -70,10 +87,13 @@ from snowflake.cli.api.project.util import (
|
|
|
70
87
|
append_test_resource_suffix,
|
|
71
88
|
extract_schema,
|
|
72
89
|
identifier_for_url,
|
|
90
|
+
to_identifier,
|
|
73
91
|
unquote_identifier,
|
|
74
92
|
)
|
|
75
93
|
from snowflake.connector import DictCursor, ProgrammingError
|
|
76
94
|
|
|
95
|
+
log = logging.getLogger(__name__)
|
|
96
|
+
|
|
77
97
|
# Reasons why an `alter application ... upgrade` might fail
|
|
78
98
|
UPGRADE_RESTRICTION_CODES = {
|
|
79
99
|
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
@@ -86,6 +106,192 @@ UPGRADE_RESTRICTION_CODES = {
|
|
|
86
106
|
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
87
107
|
|
|
88
108
|
|
|
109
|
+
class EventSharingHandler:
|
|
110
|
+
"""
|
|
111
|
+
Handles the logic around event sharing for applications.
|
|
112
|
+
This class is used to determine whether event sharing should be authorized or not, and which events should be shared.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
telemetry_definition: Optional[EventSharingTelemetry],
|
|
119
|
+
deploy_root: Path,
|
|
120
|
+
install_method: SameAccountInstallMethod,
|
|
121
|
+
console: AbstractConsole,
|
|
122
|
+
):
|
|
123
|
+
"""
|
|
124
|
+
Initializes the event sharing handler.
|
|
125
|
+
If telemetry_definition is not present or share_mandatory_events is not set,
|
|
126
|
+
we will default to sharing events if mandatory events are present in the manifest file and we are in dev mode.
|
|
127
|
+
|
|
128
|
+
:param telemetry_definition: The telemetry configuration for this application from the PDF if present.
|
|
129
|
+
:param deploy_root: The root directory of the application package.
|
|
130
|
+
:param install_method: The install method of the application.
|
|
131
|
+
:param console: The console to use for logging.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
self._is_dev_mode = install_method.is_dev_mode
|
|
135
|
+
self._metrics = get_cli_context().metrics
|
|
136
|
+
self._console = console
|
|
137
|
+
connection = get_sql_executor()._conn # noqa: SLF001
|
|
138
|
+
self._event_sharing_enabled = (
|
|
139
|
+
get_ui_parameter(
|
|
140
|
+
connection, UIParameter.NA_EVENT_SHARING_V2, "true"
|
|
141
|
+
).lower()
|
|
142
|
+
== "true"
|
|
143
|
+
)
|
|
144
|
+
self._event_sharing_enforced = (
|
|
145
|
+
get_ui_parameter(
|
|
146
|
+
connection, UIParameter.NA_ENFORCE_MANDATORY_FILTERS, "true"
|
|
147
|
+
).lower()
|
|
148
|
+
== "true"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self._share_mandatory_events = (
|
|
152
|
+
telemetry_definition and telemetry_definition.share_mandatory_events
|
|
153
|
+
)
|
|
154
|
+
self._optional_shared_events = (
|
|
155
|
+
telemetry_definition and telemetry_definition.optional_shared_events
|
|
156
|
+
) or []
|
|
157
|
+
|
|
158
|
+
self._metrics.set_counter(
|
|
159
|
+
CLICounterField.EVENT_SHARING, int(self._share_mandatory_events or False)
|
|
160
|
+
)
|
|
161
|
+
self._metrics.set_counter(CLICounterField.EVENT_SHARING_WARNING, 0)
|
|
162
|
+
self._metrics.set_counter(CLICounterField.EVENT_SHARING_ERROR, 0)
|
|
163
|
+
|
|
164
|
+
if not self._event_sharing_enabled:
|
|
165
|
+
# We cannot set AUTHORIZE_TELEMETRY_EVENT_SHARING to True or False if event sharing is not enabled,
|
|
166
|
+
# so we will ignore the field in both cases, but warn only if it is set to True
|
|
167
|
+
if self._share_mandatory_events or self._optional_shared_events:
|
|
168
|
+
self.event_sharing_warning(
|
|
169
|
+
"Same-account event sharing is not enabled in your account, therefore, application telemetry section will be ignored."
|
|
170
|
+
)
|
|
171
|
+
self._share_mandatory_events = None
|
|
172
|
+
self._optional_shared_events = []
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
if install_method.is_dev_mode:
|
|
176
|
+
mandatory_events_in_manifest = self._contains_mandatory_events(
|
|
177
|
+
find_events_definitions_in_manifest_file(deploy_root)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if mandatory_events_in_manifest and self._event_sharing_enforced:
|
|
181
|
+
if self._share_mandatory_events is None:
|
|
182
|
+
self.event_sharing_warning(
|
|
183
|
+
"Mandatory events are present in the manifest file. Automatically authorizing event sharing in dev mode. To suppress this warning, please add 'share_mandatory_events: true' in the application telemetry section."
|
|
184
|
+
)
|
|
185
|
+
self._share_mandatory_events = True
|
|
186
|
+
|
|
187
|
+
def _contains_mandatory_events(self, events_definitions: List[Dict[str, str]]):
|
|
188
|
+
return any(event["sharing"] == "MANDATORY" for event in events_definitions)
|
|
189
|
+
|
|
190
|
+
def should_authorize_event_sharing_during_create(
|
|
191
|
+
self,
|
|
192
|
+
) -> Optional[bool]:
|
|
193
|
+
"""
|
|
194
|
+
Determines whether event sharing should be authorized during the creation of the application object.
|
|
195
|
+
|
|
196
|
+
Outputs:
|
|
197
|
+
- None: Event sharing should not be updated or explicitly set.
|
|
198
|
+
- True: Event sharing should be authorized.
|
|
199
|
+
- False: Event sharing should be disabled.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if not self._event_sharing_enabled:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
return self._share_mandatory_events
|
|
206
|
+
|
|
207
|
+
def should_authorize_event_sharing_after_upgrade(
|
|
208
|
+
self,
|
|
209
|
+
upgraded_app_properties: Dict[str, str],
|
|
210
|
+
) -> Optional[bool]:
|
|
211
|
+
"""
|
|
212
|
+
Determines whether event sharing should be authorized after upgrading the application object.
|
|
213
|
+
|
|
214
|
+
:param upgraded_app_properties: The properties of the application after upgrading.
|
|
215
|
+
|
|
216
|
+
Outputs:
|
|
217
|
+
- None: Event sharing should not be updated or explicitly set.
|
|
218
|
+
- True: Event sharing should be authorized.
|
|
219
|
+
- False: Event sharing should be disabled.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
if not self._event_sharing_enabled:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
current_app_authorization = (
|
|
226
|
+
upgraded_app_properties.get(AUTHORIZE_TELEMETRY_COL, "false").lower()
|
|
227
|
+
== "true"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Skip the update if the current value is the same as the one we want to set
|
|
231
|
+
if current_app_authorization == self._share_mandatory_events:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
return self._share_mandatory_events
|
|
235
|
+
|
|
236
|
+
def event_sharing_warning(self, message: str):
|
|
237
|
+
"""
|
|
238
|
+
Logs a warning message about event sharing, and emits an event sharing warning metric.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
self._metrics.set_counter(CLICounterField.EVENT_SHARING_WARNING, 1)
|
|
242
|
+
self._console.warning(f"WARNING: {message}")
|
|
243
|
+
|
|
244
|
+
def event_sharing_error(self, message: str, err: Exception):
|
|
245
|
+
"""
|
|
246
|
+
Raises an error about event sharing, and emits an event sharing error metric.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
self._metrics.set_counter(CLICounterField.EVENT_SHARING_ERROR, 1)
|
|
250
|
+
raise ClickException(message) from err
|
|
251
|
+
|
|
252
|
+
def events_to_share(
|
|
253
|
+
self, events_definitions: List[Dict[str, str]]
|
|
254
|
+
) -> Optional[List[str]]:
|
|
255
|
+
"""
|
|
256
|
+
Determines which events should be shared based on the application events definitions.
|
|
257
|
+
It also combines the mandatory events with the optional shared events.
|
|
258
|
+
|
|
259
|
+
:param events_definitions: The events definitions of the application.
|
|
260
|
+
|
|
261
|
+
Outputs:
|
|
262
|
+
- None: No events should be updated.
|
|
263
|
+
- List[str]: The names of the events that should be shared.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
# events definition has this format: [{'name': 'SNOWFLAKE$ERRORS_AND_WARNINGS', 'type': 'ERRORS_AND_WARNINGS', 'sharing': 'MANDATORY'...}]
|
|
267
|
+
events_map = {event["type"]: event for event in events_definitions}
|
|
268
|
+
events_names = []
|
|
269
|
+
for event_type in self._optional_shared_events:
|
|
270
|
+
if event_type not in events_map:
|
|
271
|
+
raise ClickException(
|
|
272
|
+
f"Event '{event_type}' from application 'telemetry' section in the project definition file could not be found in the application."
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
events_names.append(events_map[event_type]["name"])
|
|
276
|
+
|
|
277
|
+
# add mandatory events to event_names list:
|
|
278
|
+
for event in events_definitions:
|
|
279
|
+
if event["sharing"] == "MANDATORY":
|
|
280
|
+
events_names.append(event["name"])
|
|
281
|
+
|
|
282
|
+
if not self._share_mandatory_events:
|
|
283
|
+
if (
|
|
284
|
+
self._contains_mandatory_events(events_definitions)
|
|
285
|
+
and not self._event_sharing_enforced
|
|
286
|
+
):
|
|
287
|
+
self.event_sharing_warning(
|
|
288
|
+
"Mandatory events are present in the application, but event sharing is not authorized in the application telemetry field. This will soon be required to set in order to deploy this application."
|
|
289
|
+
)
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
return sorted(list(set(events_names)))
|
|
293
|
+
|
|
294
|
+
|
|
89
295
|
class ApplicationEntityModel(EntityModelBase):
|
|
90
296
|
type: Literal["application"] = DiscriminatorField() # noqa A003
|
|
91
297
|
from_: TargetField[ApplicationPackageEntityModel] = Field(
|
|
@@ -96,6 +302,10 @@ class ApplicationEntityModel(EntityModelBase):
|
|
|
96
302
|
title="Whether to enable debug mode when using a named stage to create an application object",
|
|
97
303
|
default=None,
|
|
98
304
|
)
|
|
305
|
+
telemetry: Optional[EventSharingTelemetry] = Field(
|
|
306
|
+
title="Telemetry configuration for the application",
|
|
307
|
+
default=None,
|
|
308
|
+
)
|
|
99
309
|
|
|
100
310
|
@field_validator("identifier")
|
|
101
311
|
@classmethod
|
|
@@ -116,9 +326,38 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
116
326
|
A Native App application object, created from an application package.
|
|
117
327
|
"""
|
|
118
328
|
|
|
329
|
+
@property
|
|
330
|
+
def project_root(self) -> Path:
|
|
331
|
+
return self._workspace_ctx.project_root
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def package_entity_id(self) -> str:
|
|
335
|
+
return self._entity_model.from_.target
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def name(self) -> str:
|
|
339
|
+
return to_identifier(self._entity_model.fqn.name)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def role(self) -> str:
|
|
343
|
+
model = self._entity_model
|
|
344
|
+
return (model.meta and model.meta.role) or self._workspace_ctx.default_role
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def warehouse(self) -> str:
|
|
348
|
+
model = self._entity_model
|
|
349
|
+
return (
|
|
350
|
+
model.meta and model.meta.warehouse and to_identifier(model.meta.warehouse)
|
|
351
|
+
) or to_identifier(self._workspace_ctx.default_warehouse)
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def post_deploy_hooks(self) -> list[PostDeployHook] | None:
|
|
355
|
+
model = self._entity_model
|
|
356
|
+
return model.meta and model.meta.post_deploy
|
|
357
|
+
|
|
119
358
|
def action_deploy(
|
|
120
359
|
self,
|
|
121
|
-
|
|
360
|
+
action_ctx: ActionContext,
|
|
122
361
|
from_release_directive: bool,
|
|
123
362
|
prune: bool,
|
|
124
363
|
recursive: bool,
|
|
@@ -132,136 +371,109 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
132
371
|
*args,
|
|
133
372
|
**kwargs,
|
|
134
373
|
):
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
post_deploy_hooks = model.meta.post_deploy
|
|
142
|
-
else:
|
|
143
|
-
app_role = ctx.default_role
|
|
144
|
-
app_warehouse = ctx.default_warehouse
|
|
145
|
-
post_deploy_hooks = None
|
|
146
|
-
|
|
147
|
-
package_entity: ApplicationPackageEntity = ctx.get_entity(model.from_.target)
|
|
148
|
-
package_model: ApplicationPackageEntityModel = (
|
|
149
|
-
package_entity._entity_model # noqa: SLF001
|
|
374
|
+
"""
|
|
375
|
+
Create or upgrade the application object using the given strategy
|
|
376
|
+
(unversioned dev, versioned dev, or same-account release directive).
|
|
377
|
+
"""
|
|
378
|
+
package_entity: ApplicationPackageEntity = action_ctx.get_entity(
|
|
379
|
+
self.package_entity_id
|
|
150
380
|
)
|
|
151
|
-
|
|
152
|
-
if package_model.meta and package_model.meta.role:
|
|
153
|
-
package_role = package_model.meta.role
|
|
154
|
-
else:
|
|
155
|
-
package_role = ctx.default_role
|
|
381
|
+
stage_fqn = stage_fqn or package_entity.stage_fqn
|
|
156
382
|
|
|
157
|
-
if not stage_fqn:
|
|
158
|
-
stage_fqn = f"{package_name}.{package_model.stage}"
|
|
159
|
-
stage_schema = extract_schema(stage_fqn)
|
|
160
|
-
|
|
161
|
-
is_interactive = False
|
|
162
383
|
if force:
|
|
163
384
|
policy = AllowAlwaysPolicy()
|
|
164
385
|
elif interactive:
|
|
165
|
-
is_interactive = True
|
|
166
386
|
policy = AskAlwaysPolicy()
|
|
167
387
|
else:
|
|
168
388
|
policy = DenyAlwaysPolicy()
|
|
169
389
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
recursive=True,
|
|
175
|
-
paths=[],
|
|
176
|
-
validate=validate,
|
|
390
|
+
# same-account release directive
|
|
391
|
+
if from_release_directive:
|
|
392
|
+
self.create_or_upgrade_app(
|
|
393
|
+
package=package_entity,
|
|
177
394
|
stage_fqn=stage_fqn,
|
|
395
|
+
install_method=SameAccountInstallMethod.release_directive(),
|
|
396
|
+
policy=policy,
|
|
178
397
|
interactive=interactive,
|
|
179
|
-
force=force,
|
|
180
398
|
)
|
|
399
|
+
return
|
|
181
400
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
401
|
+
# versioned dev
|
|
402
|
+
if version:
|
|
403
|
+
try:
|
|
404
|
+
version_exists = package_entity.get_existing_version_info(version)
|
|
405
|
+
if not version_exists:
|
|
406
|
+
raise UsageError(
|
|
407
|
+
f"Application package {package_entity.name} does not have any version {version} defined. Use 'snow app version create' to define a version in the application package first."
|
|
408
|
+
)
|
|
409
|
+
except ApplicationPackageDoesNotExistError as app_err:
|
|
410
|
+
raise UsageError(
|
|
411
|
+
f"Application package {package_entity.name} does not exist. Use 'snow app version create' to first create an application package and then define a version in it."
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
self.create_or_upgrade_app(
|
|
415
|
+
package=package_entity,
|
|
416
|
+
stage_fqn=stage_fqn,
|
|
417
|
+
install_method=SameAccountInstallMethod.versioned_dev(version, patch),
|
|
418
|
+
policy=policy,
|
|
419
|
+
interactive=interactive,
|
|
420
|
+
)
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
# unversioned dev
|
|
424
|
+
package_entity.action_deploy(
|
|
425
|
+
action_ctx=action_ctx,
|
|
426
|
+
prune=True,
|
|
427
|
+
recursive=True,
|
|
428
|
+
paths=[],
|
|
193
429
|
validate=validate,
|
|
194
|
-
|
|
195
|
-
|
|
430
|
+
stage_fqn=stage_fqn,
|
|
431
|
+
interactive=interactive,
|
|
432
|
+
force=force,
|
|
433
|
+
)
|
|
434
|
+
self.create_or_upgrade_app(
|
|
435
|
+
package=package_entity,
|
|
436
|
+
stage_fqn=stage_fqn,
|
|
437
|
+
install_method=SameAccountInstallMethod.unversioned_dev(),
|
|
196
438
|
policy=policy,
|
|
197
|
-
|
|
198
|
-
patch=patch,
|
|
199
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
200
|
-
deploy_package=deploy_package,
|
|
439
|
+
interactive=interactive,
|
|
201
440
|
)
|
|
202
441
|
|
|
203
442
|
def action_drop(
|
|
204
443
|
self,
|
|
205
|
-
|
|
444
|
+
action_ctx: ActionContext,
|
|
206
445
|
interactive: bool,
|
|
207
446
|
force_drop: bool = False,
|
|
208
447
|
cascade: Optional[bool] = None,
|
|
209
448
|
*args,
|
|
210
449
|
**kwargs,
|
|
211
|
-
):
|
|
212
|
-
model = self._entity_model
|
|
213
|
-
app_name = model.fqn.identifier
|
|
214
|
-
if model.meta and model.meta.role:
|
|
215
|
-
app_role = model.meta.role
|
|
216
|
-
else:
|
|
217
|
-
app_role = ctx.default_role
|
|
218
|
-
self.drop(
|
|
219
|
-
console=ctx.console,
|
|
220
|
-
app_name=app_name,
|
|
221
|
-
app_role=app_role,
|
|
222
|
-
auto_yes=force_drop,
|
|
223
|
-
interactive=interactive,
|
|
224
|
-
cascade=cascade,
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
@classmethod
|
|
228
|
-
def drop(
|
|
229
|
-
cls,
|
|
230
|
-
console: AbstractConsole,
|
|
231
|
-
app_name: str,
|
|
232
|
-
app_role: str,
|
|
233
|
-
auto_yes: bool,
|
|
234
|
-
interactive: bool = False,
|
|
235
|
-
cascade: Optional[bool] = None,
|
|
236
450
|
):
|
|
237
451
|
"""
|
|
238
452
|
Attempts to drop the application object if all validations and user prompts allow so.
|
|
239
453
|
"""
|
|
454
|
+
console = self._workspace_ctx.console
|
|
240
455
|
|
|
241
456
|
needs_confirm = True
|
|
242
457
|
|
|
243
458
|
# 1. If existing application is not found, exit gracefully
|
|
244
|
-
show_obj_row =
|
|
245
|
-
app_name=app_name,
|
|
246
|
-
app_role=app_role,
|
|
247
|
-
)
|
|
459
|
+
show_obj_row = self.get_existing_app_info()
|
|
248
460
|
if show_obj_row is None:
|
|
249
461
|
console.warning(
|
|
250
|
-
f"Role {
|
|
462
|
+
f"Role {self.role} does not own any application object with the name {self.name}, or the application object does not exist."
|
|
251
463
|
)
|
|
252
464
|
return
|
|
253
465
|
|
|
254
466
|
# 2. Check if created by the Snowflake CLI
|
|
255
467
|
row_comment = show_obj_row[COMMENT_COL]
|
|
256
468
|
if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
|
|
257
|
-
needs_confirm,
|
|
469
|
+
needs_confirm, force_drop
|
|
258
470
|
):
|
|
259
471
|
should_drop_object = typer.confirm(
|
|
260
472
|
dedent(
|
|
261
473
|
f"""\
|
|
262
|
-
Application object {
|
|
474
|
+
Application object {self.name} was not created by Snowflake CLI.
|
|
263
475
|
Application object details:
|
|
264
|
-
Name: {
|
|
476
|
+
Name: {self.name}
|
|
265
477
|
Created on: {show_obj_row["created_on"]}
|
|
266
478
|
Source: {show_obj_row["source"]}
|
|
267
479
|
Owner: {show_obj_row[OWNER_COL]}
|
|
@@ -273,7 +485,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
273
485
|
)
|
|
274
486
|
)
|
|
275
487
|
if not should_drop_object:
|
|
276
|
-
console.message(f"Did not drop application object {
|
|
488
|
+
console.message(f"Did not drop application object {self.name}.")
|
|
277
489
|
# The user desires to keep the app, therefore we can't proceed since it would
|
|
278
490
|
# leave behind an orphan app when we get to dropping the package
|
|
279
491
|
raise typer.Abort()
|
|
@@ -287,13 +499,10 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
287
499
|
interactive_prompt = ""
|
|
288
500
|
non_interactive_abort = ""
|
|
289
501
|
try:
|
|
290
|
-
if application_objects :=
|
|
291
|
-
app_name=app_name,
|
|
292
|
-
app_role=app_role,
|
|
293
|
-
):
|
|
502
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
294
503
|
has_objects_to_drop = True
|
|
295
504
|
message_prefix = (
|
|
296
|
-
f"The following objects are owned by application {
|
|
505
|
+
f"The following objects are owned by application {self.name}"
|
|
297
506
|
)
|
|
298
507
|
cascade_true_message = f"{message_prefix} and will be dropped:"
|
|
299
508
|
cascade_false_message = f"{message_prefix} and will NOT be dropped:"
|
|
@@ -303,9 +512,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
303
512
|
if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
304
513
|
raise
|
|
305
514
|
application_objects = []
|
|
306
|
-
message_prefix =
|
|
307
|
-
f"Could not determine which objects are owned by application {app_name}"
|
|
308
|
-
)
|
|
515
|
+
message_prefix = f"Could not determine which objects are owned by application {self.name}"
|
|
309
516
|
has_objects_to_drop = True # potentially, but we don't know what they are
|
|
310
517
|
cascade_true_message = (
|
|
311
518
|
f"{message_prefix}, an unknown number of objects will be dropped."
|
|
@@ -320,19 +527,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
320
527
|
console.message(cascade_true_message)
|
|
321
528
|
with console.indented():
|
|
322
529
|
for obj in application_objects:
|
|
323
|
-
console.message(
|
|
530
|
+
console.message(_application_object_to_str(obj))
|
|
324
531
|
elif cascade is False:
|
|
325
532
|
# If the user explicitly passed the --no-cascade flag
|
|
326
533
|
console.message(cascade_false_message)
|
|
327
534
|
with console.indented():
|
|
328
535
|
for obj in application_objects:
|
|
329
|
-
console.message(
|
|
536
|
+
console.message(_application_object_to_str(obj))
|
|
330
537
|
elif interactive:
|
|
331
538
|
# If the user didn't pass any cascade flag and the session is interactive
|
|
332
539
|
console.message(message_prefix)
|
|
333
540
|
with console.indented():
|
|
334
541
|
for obj in application_objects:
|
|
335
|
-
console.message(
|
|
542
|
+
console.message(_application_object_to_str(obj))
|
|
336
543
|
user_response = typer.prompt(
|
|
337
544
|
interactive_prompt,
|
|
338
545
|
show_default=False,
|
|
@@ -349,7 +556,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
349
556
|
console.message(message_prefix)
|
|
350
557
|
with console.indented():
|
|
351
558
|
for obj in application_objects:
|
|
352
|
-
console.message(
|
|
559
|
+
console.message(_application_object_to_str(obj))
|
|
353
560
|
console.message(non_interactive_abort)
|
|
354
561
|
raise typer.Abort()
|
|
355
562
|
elif cascade is None:
|
|
@@ -360,243 +567,185 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
360
567
|
drop_generic_object(
|
|
361
568
|
console=console,
|
|
362
569
|
object_type="application",
|
|
363
|
-
object_name=
|
|
364
|
-
role=
|
|
570
|
+
object_name=self.name,
|
|
571
|
+
role=self.role,
|
|
365
572
|
cascade=cascade,
|
|
366
573
|
)
|
|
367
|
-
return # The application object was successfully dropped, therefore exit gracefully
|
|
368
574
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
575
|
+
def action_events(
|
|
576
|
+
self,
|
|
577
|
+
action_ctx: ActionContext,
|
|
578
|
+
since: str | datetime | None = None,
|
|
579
|
+
until: str | datetime | None = None,
|
|
580
|
+
record_types: list[str] | None = None,
|
|
581
|
+
scopes: list[str] | None = None,
|
|
582
|
+
consumer_org: str = "",
|
|
583
|
+
consumer_account: str = "",
|
|
584
|
+
consumer_app_hash: str = "",
|
|
585
|
+
first: int = -1,
|
|
586
|
+
last: int = -1,
|
|
587
|
+
follow: bool = False,
|
|
588
|
+
interval_seconds: int = 10,
|
|
589
|
+
*args,
|
|
590
|
+
**kwargs,
|
|
591
|
+
):
|
|
592
|
+
package_entity: ApplicationPackageEntity = action_ctx.get_entity(
|
|
593
|
+
self.package_entity_id
|
|
594
|
+
)
|
|
595
|
+
if follow:
|
|
596
|
+
return self.stream_events(
|
|
597
|
+
package_name=package_entity.name,
|
|
598
|
+
interval_seconds=interval_seconds,
|
|
599
|
+
since=since,
|
|
600
|
+
record_types=record_types,
|
|
601
|
+
scopes=scopes,
|
|
602
|
+
consumer_org=consumer_org,
|
|
603
|
+
consumer_account=consumer_account,
|
|
604
|
+
consumer_app_hash=consumer_app_hash,
|
|
605
|
+
last=last,
|
|
606
|
+
)
|
|
607
|
+
else:
|
|
608
|
+
return self.get_events(
|
|
609
|
+
package_name=package_entity.name,
|
|
610
|
+
since=since,
|
|
611
|
+
until=until,
|
|
612
|
+
record_types=record_types,
|
|
613
|
+
scopes=scopes,
|
|
614
|
+
consumer_org=consumer_org,
|
|
615
|
+
consumer_account=consumer_account,
|
|
616
|
+
consumer_app_hash=consumer_app_hash,
|
|
617
|
+
first=first,
|
|
618
|
+
last=last,
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
def get_objects_owned_by_application(self) -> List[ApplicationOwnedObject]:
|
|
374
622
|
"""
|
|
375
623
|
Returns all application objects owned by this application.
|
|
376
624
|
"""
|
|
377
625
|
sql_executor = get_sql_executor()
|
|
378
|
-
with sql_executor.use_role(
|
|
626
|
+
with sql_executor.use_role(self.role):
|
|
379
627
|
results = sql_executor.execute_query(
|
|
380
|
-
f"show objects owned by application {
|
|
628
|
+
f"show objects owned by application {self.name}"
|
|
381
629
|
).fetchall()
|
|
382
630
|
return [{"name": row[1], "type": row[2]} for row in results]
|
|
383
631
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
) -> str:
|
|
388
|
-
"""
|
|
389
|
-
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
390
|
-
(COMPUTE_POOL) POOL_NAME
|
|
391
|
-
(DATABASE) DB_NAME
|
|
392
|
-
(SCHEMA) DB_NAME.PUBLIC
|
|
393
|
-
...
|
|
394
|
-
"""
|
|
395
|
-
return "\n".join(
|
|
396
|
-
[cls.application_object_to_str(obj) for obj in application_objects]
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
@staticmethod
|
|
400
|
-
def application_object_to_str(obj: ApplicationOwnedObject) -> str:
|
|
401
|
-
return f"({obj['type']}) {obj['name']}"
|
|
402
|
-
|
|
403
|
-
@classmethod
|
|
404
|
-
def deploy(
|
|
405
|
-
cls,
|
|
406
|
-
console: AbstractConsole,
|
|
407
|
-
project_root: Path,
|
|
408
|
-
app_name: str,
|
|
409
|
-
app_role: str,
|
|
410
|
-
app_warehouse: str,
|
|
411
|
-
package_name: str,
|
|
412
|
-
package_role: str,
|
|
413
|
-
stage_schema: str,
|
|
632
|
+
def create_or_upgrade_app(
|
|
633
|
+
self,
|
|
634
|
+
package: ApplicationPackageEntity,
|
|
414
635
|
stage_fqn: str,
|
|
415
|
-
|
|
416
|
-
validate: bool,
|
|
417
|
-
from_release_directive: bool,
|
|
418
|
-
is_interactive: bool,
|
|
636
|
+
install_method: SameAccountInstallMethod,
|
|
419
637
|
policy: PolicyBase,
|
|
420
|
-
|
|
421
|
-
version: Optional[str] = None,
|
|
422
|
-
patch: Optional[int] = None,
|
|
423
|
-
post_deploy_hooks: Optional[List[PostDeployHook]] = None,
|
|
424
|
-
drop_application_before_upgrade: Optional[Callable] = None,
|
|
638
|
+
interactive: bool,
|
|
425
639
|
):
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
"""
|
|
430
|
-
|
|
431
|
-
# same-account release directive
|
|
432
|
-
if from_release_directive:
|
|
433
|
-
cls.create_or_upgrade_app(
|
|
434
|
-
console=console,
|
|
435
|
-
project_root=project_root,
|
|
436
|
-
package_name=package_name,
|
|
437
|
-
package_role=package_role,
|
|
438
|
-
app_name=app_name,
|
|
439
|
-
app_role=app_role,
|
|
440
|
-
app_warehouse=app_warehouse,
|
|
441
|
-
stage_schema=stage_schema,
|
|
442
|
-
stage_fqn=stage_fqn,
|
|
443
|
-
debug_mode=debug_mode,
|
|
444
|
-
policy=policy,
|
|
445
|
-
install_method=SameAccountInstallMethod.release_directive(),
|
|
446
|
-
is_interactive=is_interactive,
|
|
447
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
448
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
449
|
-
)
|
|
450
|
-
return
|
|
640
|
+
model = self._entity_model
|
|
641
|
+
console = self._workspace_ctx.console
|
|
642
|
+
debug_mode = model.debug
|
|
451
643
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
try:
|
|
455
|
-
version_exists = ApplicationPackageEntity.get_existing_version_info(
|
|
456
|
-
version=version,
|
|
457
|
-
package_name=package_name,
|
|
458
|
-
package_role=package_role,
|
|
459
|
-
)
|
|
460
|
-
if not version_exists:
|
|
461
|
-
raise UsageError(
|
|
462
|
-
f"Application package {package_name} does not have any version {version} defined. Use 'snow app version create' to define a version in the application package first."
|
|
463
|
-
)
|
|
464
|
-
except ApplicationPackageDoesNotExistError as app_err:
|
|
465
|
-
raise UsageError(
|
|
466
|
-
f"Application package {package_name} does not exist. Use 'snow app version create' to first create an application package and then define a version in it."
|
|
467
|
-
)
|
|
644
|
+
stage_fqn = stage_fqn or package.stage_fqn
|
|
645
|
+
stage_schema = extract_schema(stage_fqn)
|
|
468
646
|
|
|
469
|
-
|
|
647
|
+
sql_executor = get_sql_executor()
|
|
648
|
+
with sql_executor.use_role(self.role):
|
|
649
|
+
event_sharing = EventSharingHandler(
|
|
650
|
+
telemetry_definition=model.telemetry,
|
|
651
|
+
deploy_root=package.deploy_root,
|
|
652
|
+
install_method=install_method,
|
|
470
653
|
console=console,
|
|
471
|
-
project_root=project_root,
|
|
472
|
-
package_name=package_name,
|
|
473
|
-
package_role=package_role,
|
|
474
|
-
app_name=app_name,
|
|
475
|
-
app_role=app_role,
|
|
476
|
-
app_warehouse=app_warehouse,
|
|
477
|
-
stage_schema=stage_schema,
|
|
478
|
-
stage_fqn=stage_fqn,
|
|
479
|
-
debug_mode=debug_mode,
|
|
480
|
-
policy=policy,
|
|
481
|
-
install_method=SameAccountInstallMethod.versioned_dev(version, patch),
|
|
482
|
-
is_interactive=is_interactive,
|
|
483
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
484
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
485
654
|
)
|
|
486
|
-
return
|
|
487
|
-
|
|
488
|
-
# unversioned dev
|
|
489
|
-
deploy_package()
|
|
490
|
-
cls.create_or_upgrade_app(
|
|
491
|
-
console=console,
|
|
492
|
-
project_root=project_root,
|
|
493
|
-
package_name=package_name,
|
|
494
|
-
package_role=package_role,
|
|
495
|
-
app_name=app_name,
|
|
496
|
-
app_role=app_role,
|
|
497
|
-
app_warehouse=app_warehouse,
|
|
498
|
-
stage_schema=stage_schema,
|
|
499
|
-
stage_fqn=stage_fqn,
|
|
500
|
-
debug_mode=debug_mode,
|
|
501
|
-
policy=policy,
|
|
502
|
-
install_method=SameAccountInstallMethod.unversioned_dev(),
|
|
503
|
-
is_interactive=is_interactive,
|
|
504
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
505
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
506
|
-
)
|
|
507
|
-
|
|
508
|
-
@classmethod
|
|
509
|
-
def create_or_upgrade_app(
|
|
510
|
-
cls,
|
|
511
|
-
console: AbstractConsole,
|
|
512
|
-
project_root: Path,
|
|
513
|
-
package_name: str,
|
|
514
|
-
package_role: str,
|
|
515
|
-
app_name: str,
|
|
516
|
-
app_role: str,
|
|
517
|
-
app_warehouse: Optional[str],
|
|
518
|
-
stage_schema: Optional[str],
|
|
519
|
-
stage_fqn: str,
|
|
520
|
-
debug_mode: bool,
|
|
521
|
-
policy: PolicyBase,
|
|
522
|
-
install_method: SameAccountInstallMethod,
|
|
523
|
-
is_interactive: bool = False,
|
|
524
|
-
post_deploy_hooks: Optional[List[PostDeployHook]] = None,
|
|
525
|
-
drop_application_before_upgrade: Optional[Callable] = None,
|
|
526
|
-
):
|
|
527
|
-
sql_executor = get_sql_executor()
|
|
528
|
-
with sql_executor.use_role(app_role):
|
|
529
655
|
|
|
530
656
|
# 1. Need to use a warehouse to create an application object
|
|
531
|
-
with sql_executor.use_warehouse(
|
|
657
|
+
with sql_executor.use_warehouse(self.warehouse):
|
|
532
658
|
|
|
533
659
|
# 2. Check for an existing application by the same name
|
|
534
|
-
show_app_row =
|
|
535
|
-
app_name=app_name,
|
|
536
|
-
app_role=app_role,
|
|
537
|
-
)
|
|
660
|
+
show_app_row = self.get_existing_app_info()
|
|
538
661
|
|
|
539
662
|
# 3. If existing application is found, perform a few validations and upgrade the application object.
|
|
540
663
|
if show_app_row:
|
|
541
|
-
|
|
542
664
|
install_method.ensure_app_usable(
|
|
543
|
-
app_name=
|
|
544
|
-
app_role=
|
|
665
|
+
app_name=self.name,
|
|
666
|
+
app_role=self.role,
|
|
545
667
|
show_app_row=show_app_row,
|
|
546
668
|
)
|
|
547
669
|
|
|
548
670
|
# If all the above checks are in order, proceed to upgrade
|
|
549
671
|
try:
|
|
550
672
|
console.step(
|
|
551
|
-
f"Upgrading existing application object {
|
|
673
|
+
f"Upgrading existing application object {self.name}."
|
|
552
674
|
)
|
|
553
675
|
using_clause = install_method.using_clause(stage_fqn)
|
|
554
676
|
upgrade_cursor = sql_executor.execute_query(
|
|
555
|
-
f"alter application {
|
|
677
|
+
f"alter application {self.name} upgrade {using_clause}",
|
|
556
678
|
)
|
|
557
679
|
print_messages(console, upgrade_cursor)
|
|
558
680
|
|
|
681
|
+
events_definitions = (
|
|
682
|
+
get_snowflake_facade().get_event_definitions(
|
|
683
|
+
self.name, self.role
|
|
684
|
+
)
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
app_properties = get_snowflake_facade().get_app_properties(
|
|
688
|
+
self.name, self.role
|
|
689
|
+
)
|
|
690
|
+
new_authorize_event_sharing_value = (
|
|
691
|
+
event_sharing.should_authorize_event_sharing_after_upgrade(
|
|
692
|
+
app_properties,
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
if new_authorize_event_sharing_value is not None:
|
|
696
|
+
log.info(
|
|
697
|
+
"Setting telemetry sharing authorization to %s",
|
|
698
|
+
new_authorize_event_sharing_value,
|
|
699
|
+
)
|
|
700
|
+
sql_executor.execute_query(
|
|
701
|
+
f"alter application {self.name} set AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(new_authorize_event_sharing_value).upper()}"
|
|
702
|
+
)
|
|
703
|
+
events_to_share = event_sharing.events_to_share(
|
|
704
|
+
events_definitions
|
|
705
|
+
)
|
|
706
|
+
if events_to_share is not None:
|
|
707
|
+
get_snowflake_facade().share_telemetry_events(
|
|
708
|
+
self.name, events_to_share
|
|
709
|
+
)
|
|
710
|
+
|
|
559
711
|
if install_method.is_dev_mode:
|
|
560
712
|
# if debug_mode is present (controlled), ensure it is up-to-date
|
|
561
713
|
if debug_mode is not None:
|
|
562
714
|
sql_executor.execute_query(
|
|
563
|
-
f"alter application {
|
|
715
|
+
f"alter application {self.name} set debug_mode = {debug_mode}"
|
|
564
716
|
)
|
|
565
717
|
|
|
566
718
|
# hooks always executed after a create or upgrade
|
|
567
|
-
|
|
568
|
-
console=console,
|
|
569
|
-
project_root=project_root,
|
|
570
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
571
|
-
app_name=app_name,
|
|
572
|
-
app_warehouse=app_warehouse,
|
|
573
|
-
)
|
|
719
|
+
self.execute_post_deploy_hooks()
|
|
574
720
|
return
|
|
575
721
|
|
|
576
722
|
except ProgrammingError as err:
|
|
577
|
-
if err.errno
|
|
578
|
-
|
|
579
|
-
|
|
723
|
+
if err.errno == CANNOT_DISABLE_MANDATORY_TELEMETRY:
|
|
724
|
+
event_sharing.event_sharing_error(
|
|
725
|
+
"Could not disable telemetry event sharing for the application because it contains mandatory events. Please set 'share_mandatory_events' to true in the application telemetry section of the project definition file.",
|
|
726
|
+
err,
|
|
727
|
+
)
|
|
728
|
+
elif err.errno in UPGRADE_RESTRICTION_CODES:
|
|
580
729
|
console.warning(err.msg)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
730
|
+
self.drop_application_before_upgrade(
|
|
731
|
+
policy=policy, interactive=interactive
|
|
732
|
+
)
|
|
733
|
+
else:
|
|
734
|
+
generic_sql_error_handler(err=err)
|
|
586
735
|
|
|
587
736
|
# 4. With no (more) existing application objects, create an application object using the release directives
|
|
588
|
-
console.step(f"Creating new application object {
|
|
737
|
+
console.step(f"Creating new application object {self.name} in account.")
|
|
589
738
|
|
|
590
|
-
if
|
|
591
|
-
with sql_executor.use_role(
|
|
739
|
+
if self.role != package.role:
|
|
740
|
+
with sql_executor.use_role(package.role):
|
|
592
741
|
sql_executor.execute_query(
|
|
593
|
-
f"grant install, develop on application package {
|
|
742
|
+
f"grant install, develop on application package {package.name} to role {self.role}"
|
|
594
743
|
)
|
|
595
744
|
sql_executor.execute_query(
|
|
596
|
-
f"grant usage on schema {
|
|
745
|
+
f"grant usage on schema {package.name}.{stage_schema} to role {self.role}"
|
|
597
746
|
)
|
|
598
747
|
sql_executor.execute_query(
|
|
599
|
-
f"grant read on stage {stage_fqn} to role {
|
|
748
|
+
f"grant read on stage {stage_fqn} to role {self.role}"
|
|
600
749
|
)
|
|
601
750
|
|
|
602
751
|
try:
|
|
@@ -609,60 +758,64 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
609
758
|
)
|
|
610
759
|
debug_mode_clause = f"debug_mode = {initial_debug_mode}"
|
|
611
760
|
|
|
761
|
+
authorize_telemetry_clause = ""
|
|
762
|
+
new_authorize_event_sharing_value = (
|
|
763
|
+
event_sharing.should_authorize_event_sharing_during_create()
|
|
764
|
+
)
|
|
765
|
+
if new_authorize_event_sharing_value is not None:
|
|
766
|
+
log.info(
|
|
767
|
+
"Setting AUTHORIZE_TELEMETRY_EVENT_SHARING to %s",
|
|
768
|
+
new_authorize_event_sharing_value,
|
|
769
|
+
)
|
|
770
|
+
authorize_telemetry_clause = f" AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(new_authorize_event_sharing_value).upper()}"
|
|
771
|
+
|
|
612
772
|
using_clause = install_method.using_clause(stage_fqn)
|
|
613
773
|
create_cursor = sql_executor.execute_query(
|
|
614
774
|
dedent(
|
|
615
775
|
f"""\
|
|
616
|
-
create application {
|
|
617
|
-
from application package {
|
|
776
|
+
create application {self.name}
|
|
777
|
+
from application package {package.name} {using_clause} {debug_mode_clause}{authorize_telemetry_clause}
|
|
618
778
|
comment = {SPECIAL_COMMENT}
|
|
619
779
|
"""
|
|
620
780
|
),
|
|
621
781
|
)
|
|
622
782
|
print_messages(console, create_cursor)
|
|
783
|
+
events_definitions = get_snowflake_facade().get_event_definitions(
|
|
784
|
+
self.name, self.role
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
events_to_share = event_sharing.events_to_share(events_definitions)
|
|
788
|
+
if events_to_share is not None:
|
|
789
|
+
get_snowflake_facade().share_telemetry_events(
|
|
790
|
+
self.name, events_to_share
|
|
791
|
+
)
|
|
623
792
|
|
|
624
793
|
# hooks always executed after a create or upgrade
|
|
625
|
-
|
|
626
|
-
console=console,
|
|
627
|
-
project_root=project_root,
|
|
628
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
629
|
-
app_name=app_name,
|
|
630
|
-
app_warehouse=app_warehouse,
|
|
631
|
-
)
|
|
794
|
+
self.execute_post_deploy_hooks()
|
|
632
795
|
|
|
633
796
|
except ProgrammingError as err:
|
|
797
|
+
if err.errno == APPLICATION_REQUIRES_TELEMETRY_SHARING:
|
|
798
|
+
event_sharing.event_sharing_error(
|
|
799
|
+
"The application package requires event sharing to be authorized. Please set 'share_mandatory_events' to true in the application telemetry section of the project definition file.",
|
|
800
|
+
err,
|
|
801
|
+
)
|
|
634
802
|
generic_sql_error_handler(err)
|
|
635
803
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
get_cli_context().metrics.set_counter_default(
|
|
646
|
-
CLICounterField.POST_DEPLOY_SCRIPTS, 0
|
|
804
|
+
def execute_post_deploy_hooks(self):
|
|
805
|
+
execute_post_deploy_hooks(
|
|
806
|
+
console=self._workspace_ctx.console,
|
|
807
|
+
project_root=self.project_root,
|
|
808
|
+
post_deploy_hooks=self.post_deploy_hooks,
|
|
809
|
+
deployed_object_type="application",
|
|
810
|
+
role_name=self.role,
|
|
811
|
+
warehouse_name=self.warehouse,
|
|
812
|
+
database_name=self.name,
|
|
647
813
|
)
|
|
648
814
|
|
|
649
|
-
if post_deploy_hooks:
|
|
650
|
-
with cls.use_application_warehouse(app_warehouse):
|
|
651
|
-
execute_post_deploy_hooks(
|
|
652
|
-
console=console,
|
|
653
|
-
project_root=project_root,
|
|
654
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
655
|
-
deployed_object_type="application",
|
|
656
|
-
database_name=app_name,
|
|
657
|
-
)
|
|
658
|
-
|
|
659
|
-
@staticmethod
|
|
660
815
|
@contextmanager
|
|
661
|
-
def use_application_warehouse(
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if app_warehouse:
|
|
665
|
-
with get_sql_executor().use_warehouse(app_warehouse):
|
|
816
|
+
def use_application_warehouse(self):
|
|
817
|
+
if self.warehouse:
|
|
818
|
+
with get_sql_executor().use_warehouse(self.warehouse):
|
|
666
819
|
yield
|
|
667
820
|
else:
|
|
668
821
|
raise ClickException(
|
|
@@ -674,25 +827,72 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
674
827
|
)
|
|
675
828
|
)
|
|
676
829
|
|
|
677
|
-
|
|
678
|
-
def get_existing_app_info(
|
|
679
|
-
app_name: str,
|
|
680
|
-
app_role: str,
|
|
681
|
-
) -> Optional[dict]:
|
|
830
|
+
def get_existing_app_info(self) -> Optional[dict]:
|
|
682
831
|
"""
|
|
683
832
|
Check for an existing application object by the same name as in project definition, in account.
|
|
684
833
|
It executes a 'show applications like' query and returns the result as single row, if one exists.
|
|
685
834
|
"""
|
|
686
835
|
sql_executor = get_sql_executor()
|
|
687
|
-
with sql_executor.use_role(
|
|
836
|
+
with sql_executor.use_role(self.role):
|
|
688
837
|
return sql_executor.show_specific_object(
|
|
689
|
-
"applications",
|
|
838
|
+
"applications", self.name, name_col=NAME_COL
|
|
690
839
|
)
|
|
691
840
|
|
|
692
|
-
|
|
841
|
+
def drop_application_before_upgrade(
|
|
842
|
+
self,
|
|
843
|
+
policy: PolicyBase,
|
|
844
|
+
interactive: bool,
|
|
845
|
+
cascade: bool = False,
|
|
846
|
+
):
|
|
847
|
+
console = self._workspace_ctx.console
|
|
848
|
+
|
|
849
|
+
if cascade:
|
|
850
|
+
try:
|
|
851
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
852
|
+
application_objects_str = _application_objects_to_str(
|
|
853
|
+
application_objects
|
|
854
|
+
)
|
|
855
|
+
console.message(
|
|
856
|
+
f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
|
|
857
|
+
)
|
|
858
|
+
except ProgrammingError as err:
|
|
859
|
+
if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
860
|
+
generic_sql_error_handler(err)
|
|
861
|
+
console.warning(
|
|
862
|
+
"The application owns other objects but they could not be determined."
|
|
863
|
+
)
|
|
864
|
+
user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
|
|
865
|
+
else:
|
|
866
|
+
user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
|
|
867
|
+
|
|
868
|
+
if not policy.should_proceed(user_prompt):
|
|
869
|
+
if interactive:
|
|
870
|
+
console.message("Not upgrading the application object.")
|
|
871
|
+
raise typer.Exit(0)
|
|
872
|
+
else:
|
|
873
|
+
console.message(
|
|
874
|
+
"Cannot upgrade the application object non-interactively without --force."
|
|
875
|
+
)
|
|
876
|
+
raise typer.Exit(1)
|
|
877
|
+
try:
|
|
878
|
+
cascade_msg = " (cascade)" if cascade else ""
|
|
879
|
+
console.step(f"Dropping application object {self.name}{cascade_msg}.")
|
|
880
|
+
cascade_sql = " cascade" if cascade else ""
|
|
881
|
+
sql_executor = get_sql_executor()
|
|
882
|
+
sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
|
|
883
|
+
except ProgrammingError as err:
|
|
884
|
+
if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
|
|
885
|
+
# We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
|
|
886
|
+
return self.drop_application_before_upgrade(
|
|
887
|
+
policy=policy,
|
|
888
|
+
interactive=interactive,
|
|
889
|
+
cascade=True,
|
|
890
|
+
)
|
|
891
|
+
else:
|
|
892
|
+
generic_sql_error_handler(err)
|
|
893
|
+
|
|
693
894
|
def get_events(
|
|
694
|
-
|
|
695
|
-
app_name: str,
|
|
895
|
+
self,
|
|
696
896
|
package_name: str,
|
|
697
897
|
since: str | datetime | None = None,
|
|
698
898
|
until: str | datetime | None = None,
|
|
@@ -704,19 +904,18 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
704
904
|
first: int = -1,
|
|
705
905
|
last: int = -1,
|
|
706
906
|
):
|
|
707
|
-
|
|
708
907
|
record_types = record_types or []
|
|
709
908
|
scopes = scopes or []
|
|
710
909
|
|
|
711
910
|
if first >= 0 and last >= 0:
|
|
712
911
|
raise ValueError("first and last cannot be used together")
|
|
713
912
|
|
|
714
|
-
account_event_table =
|
|
715
|
-
if
|
|
913
|
+
account_event_table = get_snowflake_facade().get_account_event_table()
|
|
914
|
+
if account_event_table is None:
|
|
716
915
|
raise NoEventTableForAccount()
|
|
717
916
|
|
|
718
917
|
# resource_attributes uses the unquoted/uppercase app and package name
|
|
719
|
-
app_name = unquote_identifier(
|
|
918
|
+
app_name = unquote_identifier(self.name)
|
|
720
919
|
package_name = unquote_identifier(package_name)
|
|
721
920
|
org_name = unquote_identifier(consumer_org)
|
|
722
921
|
account_name = unquote_identifier(consumer_account)
|
|
@@ -787,12 +986,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
787
986
|
try:
|
|
788
987
|
return sql_executor.execute_query(query, cursor_class=DictCursor).fetchall()
|
|
789
988
|
except ProgrammingError as err:
|
|
790
|
-
|
|
989
|
+
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
990
|
+
raise ClickException(
|
|
991
|
+
dedent(
|
|
992
|
+
f"""\
|
|
993
|
+
Event table '{account_event_table}' does not exist or you are not authorized to perform this operation.
|
|
994
|
+
Please check your EVENT_TABLE parameter to ensure that it is set to a valid event table."""
|
|
995
|
+
)
|
|
996
|
+
) from err
|
|
997
|
+
else:
|
|
998
|
+
generic_sql_error_handler(err)
|
|
791
999
|
|
|
792
|
-
@classmethod
|
|
793
1000
|
def stream_events(
|
|
794
|
-
|
|
795
|
-
app_name: str,
|
|
1001
|
+
self,
|
|
796
1002
|
package_name: str,
|
|
797
1003
|
interval_seconds: int,
|
|
798
1004
|
since: str | datetime | None = None,
|
|
@@ -804,8 +1010,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
804
1010
|
last: int = -1,
|
|
805
1011
|
) -> Generator[dict, None, None]:
|
|
806
1012
|
try:
|
|
807
|
-
events =
|
|
808
|
-
app_name=app_name,
|
|
1013
|
+
events = self.get_events(
|
|
809
1014
|
package_name=package_name,
|
|
810
1015
|
since=since,
|
|
811
1016
|
record_types=record_types,
|
|
@@ -821,8 +1026,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
821
1026
|
while True: # Then infinite poll for new events
|
|
822
1027
|
time.sleep(interval_seconds)
|
|
823
1028
|
previous_events = events
|
|
824
|
-
events =
|
|
825
|
-
app_name=app_name,
|
|
1029
|
+
events = self.get_events(
|
|
826
1030
|
package_name=package_name,
|
|
827
1031
|
since=last_event_time,
|
|
828
1032
|
record_types=record_types,
|
|
@@ -839,18 +1043,10 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
839
1043
|
except KeyboardInterrupt:
|
|
840
1044
|
return
|
|
841
1045
|
|
|
842
|
-
|
|
843
|
-
def get_account_event_table():
|
|
844
|
-
query = "show parameters like 'event_table' in account"
|
|
845
|
-
sql_executor = get_sql_executor()
|
|
846
|
-
results = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
847
|
-
return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
|
|
848
|
-
|
|
849
|
-
@classmethod
|
|
850
|
-
def get_snowsight_url(cls, app_name: str, app_warehouse: str | None) -> str:
|
|
1046
|
+
def get_snowsight_url(self) -> str:
|
|
851
1047
|
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
852
|
-
name = identifier_for_url(
|
|
853
|
-
with
|
|
1048
|
+
name = identifier_for_url(self.name)
|
|
1049
|
+
with self.use_application_warehouse():
|
|
854
1050
|
sql_executor = get_sql_executor()
|
|
855
1051
|
return make_snowsight_url(
|
|
856
1052
|
sql_executor._conn, f"/#/apps/application/{name}" # noqa: SLF001
|
|
@@ -876,3 +1072,20 @@ def _new_events_only(previous_events: list[dict], new_events: list[dict]) -> lis
|
|
|
876
1072
|
# either be in both lists or in new_events only
|
|
877
1073
|
new_events.remove(event)
|
|
878
1074
|
return new_events
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _application_objects_to_str(
|
|
1078
|
+
application_objects: list[ApplicationOwnedObject],
|
|
1079
|
+
) -> str:
|
|
1080
|
+
"""
|
|
1081
|
+
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
1082
|
+
(COMPUTE_POOL) POOL_NAME
|
|
1083
|
+
(DATABASE) DB_NAME
|
|
1084
|
+
(SCHEMA) DB_NAME.PUBLIC
|
|
1085
|
+
...
|
|
1086
|
+
"""
|
|
1087
|
+
return "\n".join([_application_object_to_str(obj) for obj in application_objects])
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def _application_object_to_str(obj: ApplicationOwnedObject) -> str:
|
|
1091
|
+
return f"({obj['type']}) {obj['name']}"
|