snowflake-cli 3.1.0__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/dev/docs/templates/usage.rst.jinja2 +1 -1
- snowflake/cli/_plugins/connection/commands.py +124 -109
- snowflake/cli/_plugins/connection/util.py +54 -9
- snowflake/cli/_plugins/cortex/manager.py +1 -1
- snowflake/cli/_plugins/git/manager.py +4 -4
- snowflake/cli/_plugins/nativeapp/artifacts.py +64 -10
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +5 -3
- snowflake/cli/_plugins/nativeapp/commands.py +10 -3
- snowflake/cli/_plugins/nativeapp/constants.py +1 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +501 -440
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +563 -885
- snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py +58 -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/compat.py +1 -89
- snowflake/cli/_plugins/nativeapp/version/commands.py +6 -3
- 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 +3 -3
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -1
- 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/manager.py +1 -1
- snowflake/cli/_plugins/spcs/services/commands.py +64 -13
- snowflake/cli/_plugins/spcs/services/manager.py +75 -15
- snowflake/cli/_plugins/sql/commands.py +9 -1
- snowflake/cli/_plugins/sql/manager.py +9 -4
- snowflake/cli/_plugins/stage/commands.py +20 -16
- snowflake/cli/_plugins/stage/diff.py +1 -1
- snowflake/cli/_plugins/stage/manager.py +140 -11
- snowflake/cli/_plugins/streamlit/manager.py +5 -5
- snowflake/cli/_plugins/workspace/commands.py +6 -3
- snowflake/cli/api/cli_global_context.py +1 -0
- snowflake/cli/api/config.py +23 -5
- snowflake/cli/api/console/console.py +4 -19
- snowflake/cli/api/entities/utils.py +19 -32
- snowflake/cli/api/errno.py +2 -0
- snowflake/cli/api/exceptions.py +9 -0
- snowflake/cli/api/metrics.py +223 -7
- snowflake/cli/api/output/types.py +1 -1
- snowflake/cli/api/project/definition_conversion.py +179 -62
- snowflake/cli/api/rest_api.py +26 -4
- snowflake/cli/api/secure_utils.py +1 -1
- snowflake/cli/api/sql_execution.py +35 -22
- snowflake/cli/api/stage_path.py +5 -2
- {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.0.dist-info}/METADATA +7 -8
- {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.0.dist-info}/RECORD +56 -55
- {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.0.dist-info}/WHEEL +1 -1
- snowflake/cli/_plugins/nativeapp/manager.py +0 -392
- snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
- snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -56
- {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.1.0.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,6 +52,7 @@ 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
57
|
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
45
58
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
@@ -54,6 +67,8 @@ from snowflake.cli.api.entities.utils import (
|
|
|
54
67
|
from snowflake.cli.api.errno import (
|
|
55
68
|
APPLICATION_NO_LONGER_AVAILABLE,
|
|
56
69
|
APPLICATION_OWNS_EXTERNAL_OBJECTS,
|
|
70
|
+
APPLICATION_REQUIRES_TELEMETRY_SHARING,
|
|
71
|
+
CANNOT_DISABLE_MANDATORY_TELEMETRY,
|
|
57
72
|
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
58
73
|
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
|
|
59
74
|
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
@@ -77,6 +92,8 @@ from snowflake.cli.api.project.util import (
|
|
|
77
92
|
)
|
|
78
93
|
from snowflake.connector import DictCursor, ProgrammingError
|
|
79
94
|
|
|
95
|
+
log = logging.getLogger(__name__)
|
|
96
|
+
|
|
80
97
|
# Reasons why an `alter application ... upgrade` might fail
|
|
81
98
|
UPGRADE_RESTRICTION_CODES = {
|
|
82
99
|
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
@@ -89,6 +106,192 @@ UPGRADE_RESTRICTION_CODES = {
|
|
|
89
106
|
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
90
107
|
|
|
91
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
|
+
|
|
92
295
|
class ApplicationEntityModel(EntityModelBase):
|
|
93
296
|
type: Literal["application"] = DiscriminatorField() # noqa A003
|
|
94
297
|
from_: TargetField[ApplicationPackageEntityModel] = Field(
|
|
@@ -99,6 +302,10 @@ class ApplicationEntityModel(EntityModelBase):
|
|
|
99
302
|
title="Whether to enable debug mode when using a named stage to create an application object",
|
|
100
303
|
default=None,
|
|
101
304
|
)
|
|
305
|
+
telemetry: Optional[EventSharingTelemetry] = Field(
|
|
306
|
+
title="Telemetry configuration for the application",
|
|
307
|
+
default=None,
|
|
308
|
+
)
|
|
102
309
|
|
|
103
310
|
@field_validator("identifier")
|
|
104
311
|
@classmethod
|
|
@@ -119,6 +326,35 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
119
326
|
A Native App application object, created from an application package.
|
|
120
327
|
"""
|
|
121
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
|
+
|
|
122
358
|
def action_deploy(
|
|
123
359
|
self,
|
|
124
360
|
action_ctx: ActionContext,
|
|
@@ -135,86 +371,72 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
135
371
|
*args,
|
|
136
372
|
**kwargs,
|
|
137
373
|
):
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if model.meta:
|
|
143
|
-
app_role = model.meta.role or workspace_ctx.default_role
|
|
144
|
-
app_warehouse = model.meta.warehouse or workspace_ctx.default_warehouse
|
|
145
|
-
post_deploy_hooks = model.meta.post_deploy
|
|
146
|
-
else:
|
|
147
|
-
app_role = workspace_ctx.default_role
|
|
148
|
-
app_warehouse = workspace_ctx.default_warehouse
|
|
149
|
-
post_deploy_hooks = None
|
|
150
|
-
|
|
374
|
+
"""
|
|
375
|
+
Create or upgrade the application object using the given strategy
|
|
376
|
+
(unversioned dev, versioned dev, or same-account release directive).
|
|
377
|
+
"""
|
|
151
378
|
package_entity: ApplicationPackageEntity = action_ctx.get_entity(
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
package_model: ApplicationPackageEntityModel = (
|
|
155
|
-
package_entity._entity_model # noqa: SLF001
|
|
379
|
+
self.package_entity_id
|
|
156
380
|
)
|
|
157
|
-
|
|
158
|
-
if package_model.meta and package_model.meta.role:
|
|
159
|
-
package_role = package_model.meta.role
|
|
160
|
-
else:
|
|
161
|
-
package_role = workspace_ctx.default_role
|
|
381
|
+
stage_fqn = stage_fqn or package_entity.stage_fqn
|
|
162
382
|
|
|
163
|
-
if not stage_fqn:
|
|
164
|
-
stage_fqn = f"{package_name}.{package_model.stage}"
|
|
165
|
-
stage_schema = extract_schema(stage_fqn)
|
|
166
|
-
|
|
167
|
-
is_interactive = False
|
|
168
383
|
if force:
|
|
169
384
|
policy = AllowAlwaysPolicy()
|
|
170
385
|
elif interactive:
|
|
171
|
-
is_interactive = True
|
|
172
386
|
policy = AskAlwaysPolicy()
|
|
173
387
|
else:
|
|
174
388
|
policy = DenyAlwaysPolicy()
|
|
175
389
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
recursive=True,
|
|
181
|
-
paths=[],
|
|
182
|
-
validate=validate,
|
|
390
|
+
# same-account release directive
|
|
391
|
+
if from_release_directive:
|
|
392
|
+
self.create_or_upgrade_app(
|
|
393
|
+
package=package_entity,
|
|
183
394
|
stage_fqn=stage_fqn,
|
|
395
|
+
install_method=SameAccountInstallMethod.release_directive(),
|
|
396
|
+
policy=policy,
|
|
184
397
|
interactive=interactive,
|
|
185
|
-
force=force,
|
|
186
398
|
)
|
|
399
|
+
return
|
|
400
|
+
|
|
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
|
+
)
|
|
187
413
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
app_role=app_role,
|
|
414
|
+
self.create_or_upgrade_app(
|
|
415
|
+
package=package_entity,
|
|
416
|
+
stage_fqn=stage_fqn,
|
|
417
|
+
install_method=SameAccountInstallMethod.versioned_dev(version, patch),
|
|
193
418
|
policy=policy,
|
|
194
|
-
|
|
195
|
-
cascade=cascade,
|
|
419
|
+
interactive=interactive,
|
|
196
420
|
)
|
|
421
|
+
return
|
|
197
422
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
package_name=package_name,
|
|
205
|
-
package_role=package_role,
|
|
206
|
-
stage_schema=stage_schema,
|
|
207
|
-
stage_fqn=stage_fqn,
|
|
208
|
-
debug_mode=debug_mode,
|
|
423
|
+
# unversioned dev
|
|
424
|
+
package_entity.action_deploy(
|
|
425
|
+
action_ctx=action_ctx,
|
|
426
|
+
prune=True,
|
|
427
|
+
recursive=True,
|
|
428
|
+
paths=[],
|
|
209
429
|
validate=validate,
|
|
210
|
-
|
|
211
|
-
|
|
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(),
|
|
212
438
|
policy=policy,
|
|
213
|
-
|
|
214
|
-
patch=patch,
|
|
215
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
216
|
-
deploy_package=deploy_package,
|
|
217
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
439
|
+
interactive=interactive,
|
|
218
440
|
)
|
|
219
441
|
|
|
220
442
|
def action_drop(
|
|
@@ -225,113 +447,33 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
225
447
|
cascade: Optional[bool] = None,
|
|
226
448
|
*args,
|
|
227
449
|
**kwargs,
|
|
228
|
-
):
|
|
229
|
-
model = self._entity_model
|
|
230
|
-
workspace_ctx = self._workspace_ctx
|
|
231
|
-
app_name = model.fqn.identifier
|
|
232
|
-
if model.meta and model.meta.role:
|
|
233
|
-
app_role = model.meta.role
|
|
234
|
-
else:
|
|
235
|
-
app_role = workspace_ctx.default_role
|
|
236
|
-
self.drop(
|
|
237
|
-
console=workspace_ctx.console,
|
|
238
|
-
app_name=app_name,
|
|
239
|
-
app_role=app_role,
|
|
240
|
-
auto_yes=force_drop,
|
|
241
|
-
interactive=interactive,
|
|
242
|
-
cascade=cascade,
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
def action_events(
|
|
246
|
-
self,
|
|
247
|
-
action_ctx: ActionContext,
|
|
248
|
-
since: str | datetime | None = None,
|
|
249
|
-
until: str | datetime | None = None,
|
|
250
|
-
record_types: list[str] | None = None,
|
|
251
|
-
scopes: list[str] | None = None,
|
|
252
|
-
consumer_org: str = "",
|
|
253
|
-
consumer_account: str = "",
|
|
254
|
-
consumer_app_hash: str = "",
|
|
255
|
-
first: int = -1,
|
|
256
|
-
last: int = -1,
|
|
257
|
-
follow: bool = False,
|
|
258
|
-
interval_seconds: int = 10,
|
|
259
|
-
*args,
|
|
260
|
-
**kwargs,
|
|
261
|
-
):
|
|
262
|
-
model = self._entity_model
|
|
263
|
-
package_entity: ApplicationPackageEntity = action_ctx.get_entity(
|
|
264
|
-
model.from_.target
|
|
265
|
-
)
|
|
266
|
-
package_model: ApplicationPackageEntityModel = (
|
|
267
|
-
package_entity._entity_model # noqa: SLF001
|
|
268
|
-
)
|
|
269
|
-
if follow:
|
|
270
|
-
return self.stream_events(
|
|
271
|
-
app_name=model.fqn.identifier,
|
|
272
|
-
package_name=package_model.fqn.identifier,
|
|
273
|
-
interval_seconds=interval_seconds,
|
|
274
|
-
since=since,
|
|
275
|
-
record_types=record_types,
|
|
276
|
-
scopes=scopes,
|
|
277
|
-
consumer_org=consumer_org,
|
|
278
|
-
consumer_account=consumer_account,
|
|
279
|
-
consumer_app_hash=consumer_app_hash,
|
|
280
|
-
last=last,
|
|
281
|
-
)
|
|
282
|
-
else:
|
|
283
|
-
return self.get_events(
|
|
284
|
-
app_name=model.fqn.identifier,
|
|
285
|
-
package_name=package_model.fqn.identifier,
|
|
286
|
-
since=since,
|
|
287
|
-
until=until,
|
|
288
|
-
record_types=record_types,
|
|
289
|
-
scopes=scopes,
|
|
290
|
-
consumer_org=consumer_org,
|
|
291
|
-
consumer_account=consumer_account,
|
|
292
|
-
consumer_app_hash=consumer_app_hash,
|
|
293
|
-
first=first,
|
|
294
|
-
last=last,
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
@classmethod
|
|
298
|
-
def drop(
|
|
299
|
-
cls,
|
|
300
|
-
console: AbstractConsole,
|
|
301
|
-
app_name: str,
|
|
302
|
-
app_role: str,
|
|
303
|
-
auto_yes: bool,
|
|
304
|
-
interactive: bool = False,
|
|
305
|
-
cascade: Optional[bool] = None,
|
|
306
450
|
):
|
|
307
451
|
"""
|
|
308
452
|
Attempts to drop the application object if all validations and user prompts allow so.
|
|
309
453
|
"""
|
|
454
|
+
console = self._workspace_ctx.console
|
|
310
455
|
|
|
311
456
|
needs_confirm = True
|
|
312
457
|
|
|
313
458
|
# 1. If existing application is not found, exit gracefully
|
|
314
|
-
show_obj_row =
|
|
315
|
-
app_name=app_name,
|
|
316
|
-
app_role=app_role,
|
|
317
|
-
)
|
|
459
|
+
show_obj_row = self.get_existing_app_info()
|
|
318
460
|
if show_obj_row is None:
|
|
319
461
|
console.warning(
|
|
320
|
-
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."
|
|
321
463
|
)
|
|
322
464
|
return
|
|
323
465
|
|
|
324
466
|
# 2. Check if created by the Snowflake CLI
|
|
325
467
|
row_comment = show_obj_row[COMMENT_COL]
|
|
326
468
|
if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
|
|
327
|
-
needs_confirm,
|
|
469
|
+
needs_confirm, force_drop
|
|
328
470
|
):
|
|
329
471
|
should_drop_object = typer.confirm(
|
|
330
472
|
dedent(
|
|
331
473
|
f"""\
|
|
332
|
-
Application object {
|
|
474
|
+
Application object {self.name} was not created by Snowflake CLI.
|
|
333
475
|
Application object details:
|
|
334
|
-
Name: {
|
|
476
|
+
Name: {self.name}
|
|
335
477
|
Created on: {show_obj_row["created_on"]}
|
|
336
478
|
Source: {show_obj_row["source"]}
|
|
337
479
|
Owner: {show_obj_row[OWNER_COL]}
|
|
@@ -343,7 +485,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
343
485
|
)
|
|
344
486
|
)
|
|
345
487
|
if not should_drop_object:
|
|
346
|
-
console.message(f"Did not drop application object {
|
|
488
|
+
console.message(f"Did not drop application object {self.name}.")
|
|
347
489
|
# The user desires to keep the app, therefore we can't proceed since it would
|
|
348
490
|
# leave behind an orphan app when we get to dropping the package
|
|
349
491
|
raise typer.Abort()
|
|
@@ -357,13 +499,10 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
357
499
|
interactive_prompt = ""
|
|
358
500
|
non_interactive_abort = ""
|
|
359
501
|
try:
|
|
360
|
-
if application_objects :=
|
|
361
|
-
app_name=app_name,
|
|
362
|
-
app_role=app_role,
|
|
363
|
-
):
|
|
502
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
364
503
|
has_objects_to_drop = True
|
|
365
504
|
message_prefix = (
|
|
366
|
-
f"The following objects are owned by application {
|
|
505
|
+
f"The following objects are owned by application {self.name}"
|
|
367
506
|
)
|
|
368
507
|
cascade_true_message = f"{message_prefix} and will be dropped:"
|
|
369
508
|
cascade_false_message = f"{message_prefix} and will NOT be dropped:"
|
|
@@ -373,9 +512,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
373
512
|
if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
374
513
|
raise
|
|
375
514
|
application_objects = []
|
|
376
|
-
message_prefix =
|
|
377
|
-
f"Could not determine which objects are owned by application {app_name}"
|
|
378
|
-
)
|
|
515
|
+
message_prefix = f"Could not determine which objects are owned by application {self.name}"
|
|
379
516
|
has_objects_to_drop = True # potentially, but we don't know what they are
|
|
380
517
|
cascade_true_message = (
|
|
381
518
|
f"{message_prefix}, an unknown number of objects will be dropped."
|
|
@@ -390,19 +527,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
390
527
|
console.message(cascade_true_message)
|
|
391
528
|
with console.indented():
|
|
392
529
|
for obj in application_objects:
|
|
393
|
-
console.message(
|
|
530
|
+
console.message(_application_object_to_str(obj))
|
|
394
531
|
elif cascade is False:
|
|
395
532
|
# If the user explicitly passed the --no-cascade flag
|
|
396
533
|
console.message(cascade_false_message)
|
|
397
534
|
with console.indented():
|
|
398
535
|
for obj in application_objects:
|
|
399
|
-
console.message(
|
|
536
|
+
console.message(_application_object_to_str(obj))
|
|
400
537
|
elif interactive:
|
|
401
538
|
# If the user didn't pass any cascade flag and the session is interactive
|
|
402
539
|
console.message(message_prefix)
|
|
403
540
|
with console.indented():
|
|
404
541
|
for obj in application_objects:
|
|
405
|
-
console.message(
|
|
542
|
+
console.message(_application_object_to_str(obj))
|
|
406
543
|
user_response = typer.prompt(
|
|
407
544
|
interactive_prompt,
|
|
408
545
|
show_default=False,
|
|
@@ -419,7 +556,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
419
556
|
console.message(message_prefix)
|
|
420
557
|
with console.indented():
|
|
421
558
|
for obj in application_objects:
|
|
422
|
-
console.message(
|
|
559
|
+
console.message(_application_object_to_str(obj))
|
|
423
560
|
console.message(non_interactive_abort)
|
|
424
561
|
raise typer.Abort()
|
|
425
562
|
elif cascade is None:
|
|
@@ -430,243 +567,185 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
430
567
|
drop_generic_object(
|
|
431
568
|
console=console,
|
|
432
569
|
object_type="application",
|
|
433
|
-
object_name=
|
|
434
|
-
role=
|
|
570
|
+
object_name=self.name,
|
|
571
|
+
role=self.role,
|
|
435
572
|
cascade=cascade,
|
|
436
573
|
)
|
|
437
|
-
return # The application object was successfully dropped, therefore exit gracefully
|
|
438
574
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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]:
|
|
444
622
|
"""
|
|
445
623
|
Returns all application objects owned by this application.
|
|
446
624
|
"""
|
|
447
625
|
sql_executor = get_sql_executor()
|
|
448
|
-
with sql_executor.use_role(
|
|
626
|
+
with sql_executor.use_role(self.role):
|
|
449
627
|
results = sql_executor.execute_query(
|
|
450
|
-
f"show objects owned by application {
|
|
628
|
+
f"show objects owned by application {self.name}"
|
|
451
629
|
).fetchall()
|
|
452
630
|
return [{"name": row[1], "type": row[2]} for row in results]
|
|
453
631
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
) -> str:
|
|
458
|
-
"""
|
|
459
|
-
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
460
|
-
(COMPUTE_POOL) POOL_NAME
|
|
461
|
-
(DATABASE) DB_NAME
|
|
462
|
-
(SCHEMA) DB_NAME.PUBLIC
|
|
463
|
-
...
|
|
464
|
-
"""
|
|
465
|
-
return "\n".join(
|
|
466
|
-
[cls.application_object_to_str(obj) for obj in application_objects]
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
@staticmethod
|
|
470
|
-
def application_object_to_str(obj: ApplicationOwnedObject) -> str:
|
|
471
|
-
return f"({obj['type']}) {obj['name']}"
|
|
472
|
-
|
|
473
|
-
@classmethod
|
|
474
|
-
def deploy(
|
|
475
|
-
cls,
|
|
476
|
-
console: AbstractConsole,
|
|
477
|
-
project_root: Path,
|
|
478
|
-
app_name: str,
|
|
479
|
-
app_role: str,
|
|
480
|
-
app_warehouse: str,
|
|
481
|
-
package_name: str,
|
|
482
|
-
package_role: str,
|
|
483
|
-
stage_schema: str,
|
|
632
|
+
def create_or_upgrade_app(
|
|
633
|
+
self,
|
|
634
|
+
package: ApplicationPackageEntity,
|
|
484
635
|
stage_fqn: str,
|
|
485
|
-
|
|
486
|
-
validate: bool,
|
|
487
|
-
from_release_directive: bool,
|
|
488
|
-
is_interactive: bool,
|
|
636
|
+
install_method: SameAccountInstallMethod,
|
|
489
637
|
policy: PolicyBase,
|
|
490
|
-
|
|
491
|
-
version: Optional[str] = None,
|
|
492
|
-
patch: Optional[int] = None,
|
|
493
|
-
post_deploy_hooks: Optional[List[PostDeployHook]] = None,
|
|
494
|
-
drop_application_before_upgrade: Optional[Callable] = None,
|
|
638
|
+
interactive: bool,
|
|
495
639
|
):
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"""
|
|
500
|
-
|
|
501
|
-
# same-account release directive
|
|
502
|
-
if from_release_directive:
|
|
503
|
-
cls.create_or_upgrade_app(
|
|
504
|
-
console=console,
|
|
505
|
-
project_root=project_root,
|
|
506
|
-
package_name=package_name,
|
|
507
|
-
package_role=package_role,
|
|
508
|
-
app_name=app_name,
|
|
509
|
-
app_role=app_role,
|
|
510
|
-
app_warehouse=app_warehouse,
|
|
511
|
-
stage_schema=stage_schema,
|
|
512
|
-
stage_fqn=stage_fqn,
|
|
513
|
-
debug_mode=debug_mode,
|
|
514
|
-
policy=policy,
|
|
515
|
-
install_method=SameAccountInstallMethod.release_directive(),
|
|
516
|
-
is_interactive=is_interactive,
|
|
517
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
518
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
519
|
-
)
|
|
520
|
-
return
|
|
640
|
+
model = self._entity_model
|
|
641
|
+
console = self._workspace_ctx.console
|
|
642
|
+
debug_mode = model.debug
|
|
521
643
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
try:
|
|
525
|
-
version_exists = ApplicationPackageEntity.get_existing_version_info(
|
|
526
|
-
version=version,
|
|
527
|
-
package_name=package_name,
|
|
528
|
-
package_role=package_role,
|
|
529
|
-
)
|
|
530
|
-
if not version_exists:
|
|
531
|
-
raise UsageError(
|
|
532
|
-
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."
|
|
533
|
-
)
|
|
534
|
-
except ApplicationPackageDoesNotExistError as app_err:
|
|
535
|
-
raise UsageError(
|
|
536
|
-
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."
|
|
537
|
-
)
|
|
644
|
+
stage_fqn = stage_fqn or package.stage_fqn
|
|
645
|
+
stage_schema = extract_schema(stage_fqn)
|
|
538
646
|
|
|
539
|
-
|
|
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,
|
|
540
653
|
console=console,
|
|
541
|
-
project_root=project_root,
|
|
542
|
-
package_name=package_name,
|
|
543
|
-
package_role=package_role,
|
|
544
|
-
app_name=app_name,
|
|
545
|
-
app_role=app_role,
|
|
546
|
-
app_warehouse=app_warehouse,
|
|
547
|
-
stage_schema=stage_schema,
|
|
548
|
-
stage_fqn=stage_fqn,
|
|
549
|
-
debug_mode=debug_mode,
|
|
550
|
-
policy=policy,
|
|
551
|
-
install_method=SameAccountInstallMethod.versioned_dev(version, patch),
|
|
552
|
-
is_interactive=is_interactive,
|
|
553
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
554
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
555
654
|
)
|
|
556
|
-
return
|
|
557
|
-
|
|
558
|
-
# unversioned dev
|
|
559
|
-
deploy_package()
|
|
560
|
-
cls.create_or_upgrade_app(
|
|
561
|
-
console=console,
|
|
562
|
-
project_root=project_root,
|
|
563
|
-
package_name=package_name,
|
|
564
|
-
package_role=package_role,
|
|
565
|
-
app_name=app_name,
|
|
566
|
-
app_role=app_role,
|
|
567
|
-
app_warehouse=app_warehouse,
|
|
568
|
-
stage_schema=stage_schema,
|
|
569
|
-
stage_fqn=stage_fqn,
|
|
570
|
-
debug_mode=debug_mode,
|
|
571
|
-
policy=policy,
|
|
572
|
-
install_method=SameAccountInstallMethod.unversioned_dev(),
|
|
573
|
-
is_interactive=is_interactive,
|
|
574
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
575
|
-
drop_application_before_upgrade=drop_application_before_upgrade,
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
@classmethod
|
|
579
|
-
def create_or_upgrade_app(
|
|
580
|
-
cls,
|
|
581
|
-
console: AbstractConsole,
|
|
582
|
-
project_root: Path,
|
|
583
|
-
package_name: str,
|
|
584
|
-
package_role: str,
|
|
585
|
-
app_name: str,
|
|
586
|
-
app_role: str,
|
|
587
|
-
app_warehouse: Optional[str],
|
|
588
|
-
stage_schema: Optional[str],
|
|
589
|
-
stage_fqn: str,
|
|
590
|
-
debug_mode: bool,
|
|
591
|
-
policy: PolicyBase,
|
|
592
|
-
install_method: SameAccountInstallMethod,
|
|
593
|
-
is_interactive: bool = False,
|
|
594
|
-
post_deploy_hooks: Optional[List[PostDeployHook]] = None,
|
|
595
|
-
drop_application_before_upgrade: Optional[Callable] = None,
|
|
596
|
-
):
|
|
597
|
-
sql_executor = get_sql_executor()
|
|
598
|
-
with sql_executor.use_role(app_role):
|
|
599
655
|
|
|
600
656
|
# 1. Need to use a warehouse to create an application object
|
|
601
|
-
with sql_executor.use_warehouse(
|
|
657
|
+
with sql_executor.use_warehouse(self.warehouse):
|
|
602
658
|
|
|
603
659
|
# 2. Check for an existing application by the same name
|
|
604
|
-
show_app_row =
|
|
605
|
-
app_name=app_name,
|
|
606
|
-
app_role=app_role,
|
|
607
|
-
)
|
|
660
|
+
show_app_row = self.get_existing_app_info()
|
|
608
661
|
|
|
609
662
|
# 3. If existing application is found, perform a few validations and upgrade the application object.
|
|
610
663
|
if show_app_row:
|
|
611
|
-
|
|
612
664
|
install_method.ensure_app_usable(
|
|
613
|
-
app_name=
|
|
614
|
-
app_role=
|
|
665
|
+
app_name=self.name,
|
|
666
|
+
app_role=self.role,
|
|
615
667
|
show_app_row=show_app_row,
|
|
616
668
|
)
|
|
617
669
|
|
|
618
670
|
# If all the above checks are in order, proceed to upgrade
|
|
619
671
|
try:
|
|
620
672
|
console.step(
|
|
621
|
-
f"Upgrading existing application object {
|
|
673
|
+
f"Upgrading existing application object {self.name}."
|
|
622
674
|
)
|
|
623
675
|
using_clause = install_method.using_clause(stage_fqn)
|
|
624
676
|
upgrade_cursor = sql_executor.execute_query(
|
|
625
|
-
f"alter application {
|
|
677
|
+
f"alter application {self.name} upgrade {using_clause}",
|
|
626
678
|
)
|
|
627
679
|
print_messages(console, upgrade_cursor)
|
|
628
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
|
+
|
|
629
711
|
if install_method.is_dev_mode:
|
|
630
712
|
# if debug_mode is present (controlled), ensure it is up-to-date
|
|
631
713
|
if debug_mode is not None:
|
|
632
714
|
sql_executor.execute_query(
|
|
633
|
-
f"alter application {
|
|
715
|
+
f"alter application {self.name} set debug_mode = {debug_mode}"
|
|
634
716
|
)
|
|
635
717
|
|
|
636
718
|
# hooks always executed after a create or upgrade
|
|
637
|
-
|
|
638
|
-
console=console,
|
|
639
|
-
project_root=project_root,
|
|
640
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
641
|
-
app_name=app_name,
|
|
642
|
-
app_warehouse=app_warehouse,
|
|
643
|
-
)
|
|
719
|
+
self.execute_post_deploy_hooks()
|
|
644
720
|
return
|
|
645
721
|
|
|
646
722
|
except ProgrammingError as err:
|
|
647
|
-
if err.errno
|
|
648
|
-
|
|
649
|
-
|
|
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:
|
|
650
729
|
console.warning(err.msg)
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
730
|
+
self.drop_application_before_upgrade(
|
|
731
|
+
policy=policy, interactive=interactive
|
|
732
|
+
)
|
|
733
|
+
else:
|
|
734
|
+
generic_sql_error_handler(err=err)
|
|
656
735
|
|
|
657
736
|
# 4. With no (more) existing application objects, create an application object using the release directives
|
|
658
|
-
console.step(f"Creating new application object {
|
|
737
|
+
console.step(f"Creating new application object {self.name} in account.")
|
|
659
738
|
|
|
660
|
-
if
|
|
661
|
-
with sql_executor.use_role(
|
|
739
|
+
if self.role != package.role:
|
|
740
|
+
with sql_executor.use_role(package.role):
|
|
662
741
|
sql_executor.execute_query(
|
|
663
|
-
f"grant install, develop on application package {
|
|
742
|
+
f"grant install, develop on application package {package.name} to role {self.role}"
|
|
664
743
|
)
|
|
665
744
|
sql_executor.execute_query(
|
|
666
|
-
f"grant usage on schema {
|
|
745
|
+
f"grant usage on schema {package.name}.{stage_schema} to role {self.role}"
|
|
667
746
|
)
|
|
668
747
|
sql_executor.execute_query(
|
|
669
|
-
f"grant read on stage {stage_fqn} to role {
|
|
748
|
+
f"grant read on stage {stage_fqn} to role {self.role}"
|
|
670
749
|
)
|
|
671
750
|
|
|
672
751
|
try:
|
|
@@ -679,60 +758,64 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
679
758
|
)
|
|
680
759
|
debug_mode_clause = f"debug_mode = {initial_debug_mode}"
|
|
681
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
|
+
|
|
682
772
|
using_clause = install_method.using_clause(stage_fqn)
|
|
683
773
|
create_cursor = sql_executor.execute_query(
|
|
684
774
|
dedent(
|
|
685
775
|
f"""\
|
|
686
|
-
create application {
|
|
687
|
-
from application package {
|
|
776
|
+
create application {self.name}
|
|
777
|
+
from application package {package.name} {using_clause} {debug_mode_clause}{authorize_telemetry_clause}
|
|
688
778
|
comment = {SPECIAL_COMMENT}
|
|
689
779
|
"""
|
|
690
780
|
),
|
|
691
781
|
)
|
|
692
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
|
+
)
|
|
693
792
|
|
|
694
793
|
# hooks always executed after a create or upgrade
|
|
695
|
-
|
|
696
|
-
console=console,
|
|
697
|
-
project_root=project_root,
|
|
698
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
699
|
-
app_name=app_name,
|
|
700
|
-
app_warehouse=app_warehouse,
|
|
701
|
-
)
|
|
794
|
+
self.execute_post_deploy_hooks()
|
|
702
795
|
|
|
703
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
|
+
)
|
|
704
802
|
generic_sql_error_handler(err)
|
|
705
803
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
get_cli_context().metrics.set_counter_default(
|
|
716
|
-
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,
|
|
717
813
|
)
|
|
718
814
|
|
|
719
|
-
if post_deploy_hooks:
|
|
720
|
-
with cls.use_application_warehouse(app_warehouse):
|
|
721
|
-
execute_post_deploy_hooks(
|
|
722
|
-
console=console,
|
|
723
|
-
project_root=project_root,
|
|
724
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
725
|
-
deployed_object_type="application",
|
|
726
|
-
database_name=app_name,
|
|
727
|
-
)
|
|
728
|
-
|
|
729
|
-
@staticmethod
|
|
730
815
|
@contextmanager
|
|
731
|
-
def use_application_warehouse(
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if app_warehouse:
|
|
735
|
-
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):
|
|
736
819
|
yield
|
|
737
820
|
else:
|
|
738
821
|
raise ClickException(
|
|
@@ -745,44 +828,32 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
745
828
|
)
|
|
746
829
|
|
|
747
830
|
def get_existing_app_info(self) -> Optional[dict]:
|
|
748
|
-
model = self._entity_model
|
|
749
|
-
ctx = self._workspace_ctx
|
|
750
|
-
role = (model.meta and model.meta.role) or ctx.default_role
|
|
751
|
-
return self.get_existing_app_info_static(model.fqn.name, role)
|
|
752
|
-
|
|
753
|
-
# Temporary static entrypoint until NativeAppManager.get_existing_app_info() is removed
|
|
754
|
-
@staticmethod
|
|
755
|
-
def get_existing_app_info_static(app_name: str, app_role: str) -> Optional[dict]:
|
|
756
831
|
"""
|
|
757
832
|
Check for an existing application object by the same name as in project definition, in account.
|
|
758
833
|
It executes a 'show applications like' query and returns the result as single row, if one exists.
|
|
759
834
|
"""
|
|
760
835
|
sql_executor = get_sql_executor()
|
|
761
|
-
with sql_executor.use_role(
|
|
836
|
+
with sql_executor.use_role(self.role):
|
|
762
837
|
return sql_executor.show_specific_object(
|
|
763
|
-
"applications",
|
|
838
|
+
"applications", self.name, name_col=NAME_COL
|
|
764
839
|
)
|
|
765
840
|
|
|
766
|
-
@classmethod
|
|
767
841
|
def drop_application_before_upgrade(
|
|
768
|
-
|
|
769
|
-
console: AbstractConsole,
|
|
770
|
-
app_name: str,
|
|
771
|
-
app_role: str,
|
|
842
|
+
self,
|
|
772
843
|
policy: PolicyBase,
|
|
773
|
-
|
|
844
|
+
interactive: bool,
|
|
774
845
|
cascade: bool = False,
|
|
775
846
|
):
|
|
847
|
+
console = self._workspace_ctx.console
|
|
848
|
+
|
|
776
849
|
if cascade:
|
|
777
850
|
try:
|
|
778
|
-
if application_objects :=
|
|
779
|
-
|
|
780
|
-
):
|
|
781
|
-
application_objects_str = cls.application_objects_to_str(
|
|
851
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
852
|
+
application_objects_str = _application_objects_to_str(
|
|
782
853
|
application_objects
|
|
783
854
|
)
|
|
784
855
|
console.message(
|
|
785
|
-
f"The following objects are owned by application {
|
|
856
|
+
f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
|
|
786
857
|
)
|
|
787
858
|
except ProgrammingError as err:
|
|
788
859
|
if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
@@ -795,7 +866,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
795
866
|
user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
|
|
796
867
|
|
|
797
868
|
if not policy.should_proceed(user_prompt):
|
|
798
|
-
if
|
|
869
|
+
if interactive:
|
|
799
870
|
console.message("Not upgrading the application object.")
|
|
800
871
|
raise typer.Exit(0)
|
|
801
872
|
else:
|
|
@@ -805,28 +876,23 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
805
876
|
raise typer.Exit(1)
|
|
806
877
|
try:
|
|
807
878
|
cascade_msg = " (cascade)" if cascade else ""
|
|
808
|
-
console.step(f"Dropping application object {
|
|
879
|
+
console.step(f"Dropping application object {self.name}{cascade_msg}.")
|
|
809
880
|
cascade_sql = " cascade" if cascade else ""
|
|
810
881
|
sql_executor = get_sql_executor()
|
|
811
|
-
sql_executor.execute_query(f"drop application {
|
|
882
|
+
sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
|
|
812
883
|
except ProgrammingError as err:
|
|
813
884
|
if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
|
|
814
885
|
# We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
|
|
815
|
-
return
|
|
816
|
-
console=console,
|
|
817
|
-
app_name=app_name,
|
|
818
|
-
app_role=app_role,
|
|
886
|
+
return self.drop_application_before_upgrade(
|
|
819
887
|
policy=policy,
|
|
820
|
-
|
|
888
|
+
interactive=interactive,
|
|
821
889
|
cascade=True,
|
|
822
890
|
)
|
|
823
891
|
else:
|
|
824
892
|
generic_sql_error_handler(err)
|
|
825
893
|
|
|
826
|
-
@classmethod
|
|
827
894
|
def get_events(
|
|
828
|
-
|
|
829
|
-
app_name: str,
|
|
895
|
+
self,
|
|
830
896
|
package_name: str,
|
|
831
897
|
since: str | datetime | None = None,
|
|
832
898
|
until: str | datetime | None = None,
|
|
@@ -844,12 +910,12 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
844
910
|
if first >= 0 and last >= 0:
|
|
845
911
|
raise ValueError("first and last cannot be used together")
|
|
846
912
|
|
|
847
|
-
account_event_table =
|
|
848
|
-
if
|
|
913
|
+
account_event_table = get_snowflake_facade().get_account_event_table()
|
|
914
|
+
if account_event_table is None:
|
|
849
915
|
raise NoEventTableForAccount()
|
|
850
916
|
|
|
851
917
|
# resource_attributes uses the unquoted/uppercase app and package name
|
|
852
|
-
app_name = unquote_identifier(
|
|
918
|
+
app_name = unquote_identifier(self.name)
|
|
853
919
|
package_name = unquote_identifier(package_name)
|
|
854
920
|
org_name = unquote_identifier(consumer_org)
|
|
855
921
|
account_name = unquote_identifier(consumer_account)
|
|
@@ -931,10 +997,8 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
931
997
|
else:
|
|
932
998
|
generic_sql_error_handler(err)
|
|
933
999
|
|
|
934
|
-
@classmethod
|
|
935
1000
|
def stream_events(
|
|
936
|
-
|
|
937
|
-
app_name: str,
|
|
1001
|
+
self,
|
|
938
1002
|
package_name: str,
|
|
939
1003
|
interval_seconds: int,
|
|
940
1004
|
since: str | datetime | None = None,
|
|
@@ -946,8 +1010,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
946
1010
|
last: int = -1,
|
|
947
1011
|
) -> Generator[dict, None, None]:
|
|
948
1012
|
try:
|
|
949
|
-
events =
|
|
950
|
-
app_name=app_name,
|
|
1013
|
+
events = self.get_events(
|
|
951
1014
|
package_name=package_name,
|
|
952
1015
|
since=since,
|
|
953
1016
|
record_types=record_types,
|
|
@@ -963,8 +1026,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
963
1026
|
while True: # Then infinite poll for new events
|
|
964
1027
|
time.sleep(interval_seconds)
|
|
965
1028
|
previous_events = events
|
|
966
|
-
events =
|
|
967
|
-
app_name=app_name,
|
|
1029
|
+
events = self.get_events(
|
|
968
1030
|
package_name=package_name,
|
|
969
1031
|
since=last_event_time,
|
|
970
1032
|
record_types=record_types,
|
|
@@ -981,28 +1043,10 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
981
1043
|
except KeyboardInterrupt:
|
|
982
1044
|
return
|
|
983
1045
|
|
|
984
|
-
@staticmethod
|
|
985
|
-
def get_account_event_table():
|
|
986
|
-
query = "show parameters like 'event_table' in account"
|
|
987
|
-
sql_executor = get_sql_executor()
|
|
988
|
-
results = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
989
|
-
return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
|
|
990
|
-
|
|
991
1046
|
def get_snowsight_url(self) -> str:
|
|
992
1047
|
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
warehouse = (
|
|
996
|
-
model.meta and model.meta.warehouse and to_identifier(model.meta.warehouse)
|
|
997
|
-
) or to_identifier(ctx.default_warehouse)
|
|
998
|
-
return self.get_snowsight_url_static(model.fqn.name, warehouse)
|
|
999
|
-
|
|
1000
|
-
# Temporary static entrypoint until NativeAppManager.get_snowsight_url() is removed
|
|
1001
|
-
@classmethod
|
|
1002
|
-
def get_snowsight_url_static(cls, app_name: str, app_warehouse: str) -> str:
|
|
1003
|
-
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
1004
|
-
name = identifier_for_url(app_name)
|
|
1005
|
-
with cls.use_application_warehouse(app_warehouse):
|
|
1048
|
+
name = identifier_for_url(self.name)
|
|
1049
|
+
with self.use_application_warehouse():
|
|
1006
1050
|
sql_executor = get_sql_executor()
|
|
1007
1051
|
return make_snowsight_url(
|
|
1008
1052
|
sql_executor._conn, f"/#/apps/application/{name}" # noqa: SLF001
|
|
@@ -1028,3 +1072,20 @@ def _new_events_only(previous_events: list[dict], new_events: list[dict]) -> lis
|
|
|
1028
1072
|
# either be in both lists or in new_events only
|
|
1029
1073
|
new_events.remove(event)
|
|
1030
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']}"
|