snowflake-cli 3.2.1__py3-none-any.whl → 3.3.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/constants.py +4 -0
- snowflake/cli/_app/snow_connector.py +12 -0
- snowflake/cli/_app/telemetry.py +10 -3
- snowflake/cli/_plugins/connection/util.py +12 -19
- snowflake/cli/_plugins/helpers/commands.py +207 -1
- snowflake/cli/_plugins/nativeapp/artifacts.py +10 -4
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +41 -17
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +7 -0
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -1
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +42 -32
- snowflake/cli/_plugins/nativeapp/commands.py +92 -2
- snowflake/cli/_plugins/nativeapp/constants.py +5 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +221 -288
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +772 -89
- snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
- snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
- snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +212 -0
- snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
- snowflake/cli/_plugins/nativeapp/release_directive/commands.py +165 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
- snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +999 -75
- snowflake/cli/_plugins/nativeapp/utils.py +11 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +5 -1
- snowflake/cli/_plugins/nativeapp/version/commands.py +31 -4
- snowflake/cli/_plugins/notebook/manager.py +4 -2
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +234 -4
- snowflake/cli/_plugins/spcs/common.py +129 -0
- snowflake/cli/_plugins/spcs/services/commands.py +134 -14
- snowflake/cli/_plugins/spcs/services/manager.py +169 -1
- snowflake/cli/_plugins/stage/manager.py +12 -4
- snowflake/cli/_plugins/streamlit/manager.py +8 -1
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +153 -2
- snowflake/cli/_plugins/workspace/commands.py +3 -2
- snowflake/cli/_plugins/workspace/manager.py +8 -4
- snowflake/cli/api/cli_global_context.py +22 -1
- snowflake/cli/api/config.py +6 -2
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/constants.py +9 -1
- snowflake/cli/api/entities/common.py +85 -0
- snowflake/cli/api/entities/utils.py +9 -8
- snowflake/cli/api/errno.py +60 -3
- snowflake/cli/api/feature_flags.py +20 -4
- snowflake/cli/api/metrics.py +21 -27
- snowflake/cli/api/project/definition_conversion.py +1 -2
- snowflake/cli/api/project/schemas/project_definition.py +27 -6
- snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
- snowflake/cli/api/project/util.py +45 -0
- snowflake/cli/api/rest_api.py +3 -2
- {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/METADATA +13 -13
- {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/RECORD +56 -51
- {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/WHEEL +1 -1
- {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,7 +13,6 @@ from click import ClickException, UsageError
|
|
|
13
13
|
from pydantic import Field, field_validator
|
|
14
14
|
from snowflake.cli._plugins.connection.util import (
|
|
15
15
|
UIParameter,
|
|
16
|
-
get_ui_parameter,
|
|
17
16
|
make_snowsight_url,
|
|
18
17
|
)
|
|
19
18
|
from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
@@ -26,11 +25,8 @@ from snowflake.cli._plugins.nativeapp.common_flags import (
|
|
|
26
25
|
)
|
|
27
26
|
from snowflake.cli._plugins.nativeapp.constants import (
|
|
28
27
|
ALLOWED_SPECIAL_COMMENTS,
|
|
29
|
-
AUTHORIZE_TELEMETRY_COL,
|
|
30
28
|
COMMENT_COL,
|
|
31
|
-
NAME_COL,
|
|
32
29
|
OWNER_COL,
|
|
33
|
-
SPECIAL_COMMENT,
|
|
34
30
|
)
|
|
35
31
|
from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
36
32
|
ApplicationPackageEntity,
|
|
@@ -53,11 +49,19 @@ from snowflake.cli._plugins.nativeapp.same_account_install_method import (
|
|
|
53
49
|
SameAccountInstallMethod,
|
|
54
50
|
)
|
|
55
51
|
from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
|
|
52
|
+
from snowflake.cli._plugins.nativeapp.sf_facade_exceptions import (
|
|
53
|
+
UpgradeApplicationRestrictionError,
|
|
54
|
+
)
|
|
56
55
|
from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
|
|
57
56
|
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
58
|
-
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
57
|
+
from snowflake.cli.api.cli_global_context import get_cli_context, span
|
|
59
58
|
from snowflake.cli.api.console.abc import AbstractConsole
|
|
60
|
-
from snowflake.cli.api.
|
|
59
|
+
from snowflake.cli.api.constants import ObjectType
|
|
60
|
+
from snowflake.cli.api.entities.common import (
|
|
61
|
+
EntityBase,
|
|
62
|
+
attach_spans_to_entity_actions,
|
|
63
|
+
get_sql_executor,
|
|
64
|
+
)
|
|
61
65
|
from snowflake.cli.api.entities.utils import (
|
|
62
66
|
drop_generic_object,
|
|
63
67
|
execute_post_deploy_hooks,
|
|
@@ -67,13 +71,7 @@ from snowflake.cli.api.entities.utils import (
|
|
|
67
71
|
from snowflake.cli.api.errno import (
|
|
68
72
|
APPLICATION_NO_LONGER_AVAILABLE,
|
|
69
73
|
APPLICATION_OWNS_EXTERNAL_OBJECTS,
|
|
70
|
-
APPLICATION_REQUIRES_TELEMETRY_SHARING,
|
|
71
|
-
CANNOT_DISABLE_MANDATORY_TELEMETRY,
|
|
72
|
-
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
73
|
-
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
|
|
74
74
|
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
75
|
-
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
76
|
-
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
77
75
|
)
|
|
78
76
|
from snowflake.cli.api.metrics import CLICounterField
|
|
79
77
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
@@ -94,15 +92,6 @@ from snowflake.connector import DictCursor, ProgrammingError
|
|
|
94
92
|
|
|
95
93
|
log = logging.getLogger(__name__)
|
|
96
94
|
|
|
97
|
-
# Reasons why an `alter application ... upgrade` might fail
|
|
98
|
-
UPGRADE_RESTRICTION_CODES = {
|
|
99
|
-
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
100
|
-
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
|
|
101
|
-
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
102
|
-
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
103
|
-
APPLICATION_NO_LONGER_AVAILABLE,
|
|
104
|
-
}
|
|
105
|
-
|
|
106
95
|
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
107
96
|
|
|
108
97
|
|
|
@@ -134,18 +123,12 @@ class EventSharingHandler:
|
|
|
134
123
|
self._is_dev_mode = install_method.is_dev_mode
|
|
135
124
|
self._metrics = get_cli_context().metrics
|
|
136
125
|
self._console = console
|
|
137
|
-
|
|
138
|
-
self._event_sharing_enabled = (
|
|
139
|
-
|
|
140
|
-
connection, UIParameter.NA_EVENT_SHARING_V2, "true"
|
|
141
|
-
).lower()
|
|
142
|
-
== "true"
|
|
126
|
+
|
|
127
|
+
self._event_sharing_enabled = get_snowflake_facade().get_ui_parameter(
|
|
128
|
+
UIParameter.NA_EVENT_SHARING_V2, True
|
|
143
129
|
)
|
|
144
|
-
self._event_sharing_enforced = (
|
|
145
|
-
|
|
146
|
-
connection, UIParameter.NA_ENFORCE_MANDATORY_FILTERS, "true"
|
|
147
|
-
).lower()
|
|
148
|
-
== "true"
|
|
130
|
+
self._event_sharing_enforced = get_snowflake_facade().get_ui_parameter(
|
|
131
|
+
UIParameter.NA_ENFORCE_MANDATORY_FILTERS, True
|
|
149
132
|
)
|
|
150
133
|
|
|
151
134
|
self._share_mandatory_events = (
|
|
@@ -187,31 +170,11 @@ class EventSharingHandler:
|
|
|
187
170
|
def _contains_mandatory_events(self, events_definitions: List[Dict[str, str]]):
|
|
188
171
|
return any(event["sharing"] == "MANDATORY" for event in events_definitions)
|
|
189
172
|
|
|
190
|
-
def
|
|
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(
|
|
173
|
+
def should_authorize_event_sharing(
|
|
208
174
|
self,
|
|
209
|
-
upgraded_app_properties: Dict[str, str],
|
|
210
175
|
) -> Optional[bool]:
|
|
211
176
|
"""
|
|
212
|
-
Determines whether event sharing should be authorized
|
|
213
|
-
|
|
214
|
-
:param upgraded_app_properties: The properties of the application after upgrading.
|
|
177
|
+
Determines whether event sharing should be authorized.
|
|
215
178
|
|
|
216
179
|
Outputs:
|
|
217
180
|
- None: Event sharing should not be updated or explicitly set.
|
|
@@ -222,15 +185,6 @@ class EventSharingHandler:
|
|
|
222
185
|
if not self._event_sharing_enabled:
|
|
223
186
|
return None
|
|
224
187
|
|
|
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
188
|
return self._share_mandatory_events
|
|
235
189
|
|
|
236
190
|
def event_sharing_warning(self, message: str):
|
|
@@ -321,6 +275,7 @@ class ApplicationEntityModel(EntityModelBase):
|
|
|
321
275
|
return with_suffix
|
|
322
276
|
|
|
323
277
|
|
|
278
|
+
@attach_spans_to_entity_actions(entity_name="app")
|
|
324
279
|
class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
325
280
|
"""
|
|
326
281
|
A Native App application object, created from an application package.
|
|
@@ -355,6 +310,18 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
355
310
|
model = self._entity_model
|
|
356
311
|
return model.meta and model.meta.post_deploy
|
|
357
312
|
|
|
313
|
+
@property
|
|
314
|
+
def console(self) -> AbstractConsole:
|
|
315
|
+
return self._workspace_ctx.console
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def debug(self) -> bool | None:
|
|
319
|
+
return self._entity_model.debug
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def telemetry(self) -> EventSharingTelemetry | None:
|
|
323
|
+
return self._entity_model.telemetry
|
|
324
|
+
|
|
358
325
|
def action_deploy(
|
|
359
326
|
self,
|
|
360
327
|
action_ctx: ActionContext,
|
|
@@ -362,6 +329,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
362
329
|
prune: bool,
|
|
363
330
|
recursive: bool,
|
|
364
331
|
paths: List[Path],
|
|
332
|
+
release_channel: Optional[str] = None,
|
|
365
333
|
validate: bool = ValidateOption,
|
|
366
334
|
stage_fqn: Optional[str] = None,
|
|
367
335
|
interactive: bool = InteractiveOption,
|
|
@@ -389,15 +357,25 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
389
357
|
|
|
390
358
|
# same-account release directive
|
|
391
359
|
if from_release_directive:
|
|
360
|
+
release_channel = package_entity.get_sanitized_release_channel(
|
|
361
|
+
release_channel
|
|
362
|
+
)
|
|
363
|
+
|
|
392
364
|
self.create_or_upgrade_app(
|
|
393
365
|
package=package_entity,
|
|
394
366
|
stage_fqn=stage_fqn,
|
|
395
367
|
install_method=SameAccountInstallMethod.release_directive(),
|
|
368
|
+
release_channel=release_channel,
|
|
396
369
|
policy=policy,
|
|
397
370
|
interactive=interactive,
|
|
398
371
|
)
|
|
399
372
|
return
|
|
400
373
|
|
|
374
|
+
if release_channel:
|
|
375
|
+
raise UsageError(
|
|
376
|
+
f"Release channel is only supported when --from-release-directive is used."
|
|
377
|
+
)
|
|
378
|
+
|
|
401
379
|
# versioned dev
|
|
402
380
|
if version:
|
|
403
381
|
try:
|
|
@@ -451,14 +429,14 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
451
429
|
"""
|
|
452
430
|
Attempts to drop the application object if all validations and user prompts allow so.
|
|
453
431
|
"""
|
|
454
|
-
console = self._workspace_ctx.console
|
|
455
|
-
|
|
456
432
|
needs_confirm = True
|
|
457
433
|
|
|
458
434
|
# 1. If existing application is not found, exit gracefully
|
|
459
|
-
show_obj_row =
|
|
435
|
+
show_obj_row = get_snowflake_facade().get_existing_app_info(
|
|
436
|
+
self.name, self.role
|
|
437
|
+
)
|
|
460
438
|
if show_obj_row is None:
|
|
461
|
-
console.warning(
|
|
439
|
+
self.console.warning(
|
|
462
440
|
f"Role {self.role} does not own any application object with the name {self.name}, or the application object does not exist."
|
|
463
441
|
)
|
|
464
442
|
return
|
|
@@ -485,7 +463,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
485
463
|
)
|
|
486
464
|
)
|
|
487
465
|
if not should_drop_object:
|
|
488
|
-
console.message(f"Did not drop application object {self.name}.")
|
|
466
|
+
self.console.message(f"Did not drop application object {self.name}.")
|
|
489
467
|
# The user desires to keep the app, therefore we can't proceed since it would
|
|
490
468
|
# leave behind an orphan app when we get to dropping the package
|
|
491
469
|
raise typer.Abort()
|
|
@@ -524,22 +502,22 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
524
502
|
if has_objects_to_drop:
|
|
525
503
|
if cascade is True:
|
|
526
504
|
# If the user explicitly passed the --cascade flag
|
|
527
|
-
console.message(cascade_true_message)
|
|
528
|
-
with console.indented():
|
|
505
|
+
self.console.message(cascade_true_message)
|
|
506
|
+
with self.console.indented():
|
|
529
507
|
for obj in application_objects:
|
|
530
|
-
console.message(_application_object_to_str(obj))
|
|
508
|
+
self.console.message(_application_object_to_str(obj))
|
|
531
509
|
elif cascade is False:
|
|
532
510
|
# If the user explicitly passed the --no-cascade flag
|
|
533
|
-
console.message(cascade_false_message)
|
|
534
|
-
with console.indented():
|
|
511
|
+
self.console.message(cascade_false_message)
|
|
512
|
+
with self.console.indented():
|
|
535
513
|
for obj in application_objects:
|
|
536
|
-
console.message(_application_object_to_str(obj))
|
|
514
|
+
self.console.message(_application_object_to_str(obj))
|
|
537
515
|
elif interactive:
|
|
538
516
|
# If the user didn't pass any cascade flag and the session is interactive
|
|
539
|
-
console.message(message_prefix)
|
|
540
|
-
with console.indented():
|
|
517
|
+
self.console.message(message_prefix)
|
|
518
|
+
with self.console.indented():
|
|
541
519
|
for obj in application_objects:
|
|
542
|
-
console.message(_application_object_to_str(obj))
|
|
520
|
+
self.console.message(_application_object_to_str(obj))
|
|
543
521
|
user_response = typer.prompt(
|
|
544
522
|
interactive_prompt,
|
|
545
523
|
show_default=False,
|
|
@@ -553,11 +531,11 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
553
531
|
raise typer.Abort()
|
|
554
532
|
else:
|
|
555
533
|
# Else abort since we don't know what to do and can't ask the user
|
|
556
|
-
console.message(message_prefix)
|
|
557
|
-
with console.indented():
|
|
534
|
+
self.console.message(message_prefix)
|
|
535
|
+
with self.console.indented():
|
|
558
536
|
for obj in application_objects:
|
|
559
|
-
console.message(_application_object_to_str(obj))
|
|
560
|
-
console.message(non_interactive_abort)
|
|
537
|
+
self.console.message(_application_object_to_str(obj))
|
|
538
|
+
self.console.message(non_interactive_abort)
|
|
561
539
|
raise typer.Abort()
|
|
562
540
|
elif cascade is None:
|
|
563
541
|
# If there's nothing to drop, set cascade to an explicit False value
|
|
@@ -565,7 +543,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
565
543
|
|
|
566
544
|
# 4. All validations have passed, drop object
|
|
567
545
|
drop_generic_object(
|
|
568
|
-
console=console,
|
|
546
|
+
console=self.console,
|
|
569
547
|
object_type="application",
|
|
570
548
|
object_name=self.name,
|
|
571
549
|
role=self.role,
|
|
@@ -629,181 +607,145 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
629
607
|
).fetchall()
|
|
630
608
|
return [{"name": row[1], "type": row[2]} for row in results]
|
|
631
609
|
|
|
632
|
-
def
|
|
610
|
+
def _upgrade_app(
|
|
633
611
|
self,
|
|
634
|
-
package: ApplicationPackageEntity,
|
|
635
612
|
stage_fqn: str,
|
|
636
613
|
install_method: SameAccountInstallMethod,
|
|
614
|
+
event_sharing: EventSharingHandler,
|
|
637
615
|
policy: PolicyBase,
|
|
638
616
|
interactive: bool,
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
console
|
|
642
|
-
debug_mode = model.debug
|
|
643
|
-
|
|
644
|
-
stage_fqn = stage_fqn or package.stage_fqn
|
|
645
|
-
stage_schema = extract_schema(stage_fqn)
|
|
617
|
+
release_channel: Optional[str] = None,
|
|
618
|
+
) -> list[tuple[str]] | None:
|
|
619
|
+
self.console.step(f"Upgrading existing application object {self.name}.")
|
|
646
620
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
telemetry_definition=model.telemetry,
|
|
651
|
-
deploy_root=package.deploy_root,
|
|
621
|
+
try:
|
|
622
|
+
return get_snowflake_facade().upgrade_application(
|
|
623
|
+
name=self.name,
|
|
652
624
|
install_method=install_method,
|
|
653
|
-
|
|
625
|
+
stage_fqn=stage_fqn,
|
|
626
|
+
debug_mode=self.debug,
|
|
627
|
+
should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
|
|
628
|
+
release_channel=release_channel,
|
|
629
|
+
role=self.role,
|
|
630
|
+
warehouse=self.warehouse,
|
|
654
631
|
)
|
|
632
|
+
except UpgradeApplicationRestrictionError as err:
|
|
633
|
+
self.console.warning(err.message)
|
|
634
|
+
self.drop_application_before_upgrade(policy=policy, interactive=interactive)
|
|
635
|
+
return None
|
|
655
636
|
|
|
656
|
-
|
|
657
|
-
|
|
637
|
+
def _create_app(
|
|
638
|
+
self,
|
|
639
|
+
stage_fqn: str,
|
|
640
|
+
install_method: SameAccountInstallMethod,
|
|
641
|
+
event_sharing: EventSharingHandler,
|
|
642
|
+
package: ApplicationPackageEntity,
|
|
643
|
+
release_channel: Optional[str] = None,
|
|
644
|
+
) -> list[tuple[str]]:
|
|
645
|
+
self.console.step(f"Creating new application object {self.name} in account.")
|
|
646
|
+
|
|
647
|
+
if package.role != self.role:
|
|
648
|
+
get_snowflake_facade().grant_privileges_to_role(
|
|
649
|
+
privileges=["install", "develop"],
|
|
650
|
+
object_type=ObjectType.APPLICATION_PACKAGE,
|
|
651
|
+
object_identifier=package.name,
|
|
652
|
+
role_to_grant=self.role,
|
|
653
|
+
role_to_use=package.role,
|
|
654
|
+
)
|
|
658
655
|
|
|
659
|
-
|
|
660
|
-
|
|
656
|
+
stage_schema = extract_schema(stage_fqn)
|
|
657
|
+
get_snowflake_facade().grant_privileges_to_role(
|
|
658
|
+
privileges=["usage"],
|
|
659
|
+
object_type=ObjectType.SCHEMA,
|
|
660
|
+
object_identifier=f"{package.name}.{stage_schema}",
|
|
661
|
+
role_to_grant=self.role,
|
|
662
|
+
role_to_use=package.role,
|
|
663
|
+
)
|
|
661
664
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
665
|
+
get_snowflake_facade().grant_privileges_to_role(
|
|
666
|
+
privileges=["read"],
|
|
667
|
+
object_type=ObjectType.STAGE,
|
|
668
|
+
object_identifier=stage_fqn,
|
|
669
|
+
role_to_grant=self.role,
|
|
670
|
+
role_to_use=package.role,
|
|
671
|
+
)
|
|
669
672
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
673
|
+
return get_snowflake_facade().create_application(
|
|
674
|
+
name=self.name,
|
|
675
|
+
package_name=package.name,
|
|
676
|
+
install_method=install_method,
|
|
677
|
+
stage_fqn=stage_fqn,
|
|
678
|
+
debug_mode=self.debug,
|
|
679
|
+
should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
|
|
680
|
+
role=self.role,
|
|
681
|
+
warehouse=self.warehouse,
|
|
682
|
+
release_channel=release_channel,
|
|
683
|
+
)
|
|
680
684
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
685
|
+
@span("update_app_object")
|
|
686
|
+
def create_or_upgrade_app(
|
|
687
|
+
self,
|
|
688
|
+
package: ApplicationPackageEntity,
|
|
689
|
+
stage_fqn: str,
|
|
690
|
+
install_method: SameAccountInstallMethod,
|
|
691
|
+
policy: PolicyBase,
|
|
692
|
+
interactive: bool,
|
|
693
|
+
release_channel: Optional[str] = None,
|
|
694
|
+
):
|
|
695
|
+
event_sharing = EventSharingHandler(
|
|
696
|
+
telemetry_definition=self.telemetry,
|
|
697
|
+
deploy_root=package.deploy_root,
|
|
698
|
+
install_method=install_method,
|
|
699
|
+
console=self.console,
|
|
700
|
+
)
|
|
686
701
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
711
|
-
if install_method.is_dev_mode:
|
|
712
|
-
# if debug_mode is present (controlled), ensure it is up-to-date
|
|
713
|
-
if debug_mode is not None:
|
|
714
|
-
sql_executor.execute_query(
|
|
715
|
-
f"alter application {self.name} set debug_mode = {debug_mode}"
|
|
716
|
-
)
|
|
717
|
-
|
|
718
|
-
# hooks always executed after a create or upgrade
|
|
719
|
-
self.execute_post_deploy_hooks()
|
|
720
|
-
return
|
|
721
|
-
|
|
722
|
-
except ProgrammingError as err:
|
|
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:
|
|
729
|
-
console.warning(err.msg)
|
|
730
|
-
self.drop_application_before_upgrade(
|
|
731
|
-
policy=policy, interactive=interactive
|
|
732
|
-
)
|
|
733
|
-
else:
|
|
734
|
-
generic_sql_error_handler(err=err)
|
|
735
|
-
|
|
736
|
-
# 4. With no (more) existing application objects, create an application object using the release directives
|
|
737
|
-
console.step(f"Creating new application object {self.name} in account.")
|
|
738
|
-
|
|
739
|
-
if self.role != package.role:
|
|
740
|
-
with sql_executor.use_role(package.role):
|
|
741
|
-
sql_executor.execute_query(
|
|
742
|
-
f"grant install, develop on application package {package.name} to role {self.role}"
|
|
743
|
-
)
|
|
744
|
-
sql_executor.execute_query(
|
|
745
|
-
f"grant usage on schema {package.name}.{stage_schema} to role {self.role}"
|
|
746
|
-
)
|
|
747
|
-
sql_executor.execute_query(
|
|
748
|
-
f"grant read on stage {stage_fqn} to role {self.role}"
|
|
749
|
-
)
|
|
702
|
+
# 1. Check for an existing application by the same name
|
|
703
|
+
show_app_row = get_snowflake_facade().get_existing_app_info(
|
|
704
|
+
self.name, self.role
|
|
705
|
+
)
|
|
750
706
|
|
|
751
|
-
|
|
752
|
-
# by default, applications are created in debug mode when possible;
|
|
753
|
-
# this can be overridden in the project definition
|
|
754
|
-
debug_mode_clause = ""
|
|
755
|
-
if install_method.is_dev_mode:
|
|
756
|
-
initial_debug_mode = (
|
|
757
|
-
debug_mode if debug_mode is not None else True
|
|
758
|
-
)
|
|
759
|
-
debug_mode_clause = f"debug_mode = {initial_debug_mode}"
|
|
707
|
+
stage_fqn = stage_fqn or package.stage_fqn
|
|
760
708
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
using_clause = install_method.using_clause(stage_fqn)
|
|
773
|
-
create_cursor = sql_executor.execute_query(
|
|
774
|
-
dedent(
|
|
775
|
-
f"""\
|
|
776
|
-
create application {self.name}
|
|
777
|
-
from application package {package.name} {using_clause} {debug_mode_clause}{authorize_telemetry_clause}
|
|
778
|
-
comment = {SPECIAL_COMMENT}
|
|
779
|
-
"""
|
|
780
|
-
),
|
|
781
|
-
)
|
|
782
|
-
print_messages(console, create_cursor)
|
|
783
|
-
events_definitions = get_snowflake_facade().get_event_definitions(
|
|
784
|
-
self.name, self.role
|
|
785
|
-
)
|
|
709
|
+
# 2. If existing application is found, try to upgrade the application object.
|
|
710
|
+
create_or_upgrade_result = None
|
|
711
|
+
if show_app_row:
|
|
712
|
+
create_or_upgrade_result = self._upgrade_app(
|
|
713
|
+
stage_fqn=stage_fqn,
|
|
714
|
+
install_method=install_method,
|
|
715
|
+
event_sharing=event_sharing,
|
|
716
|
+
policy=policy,
|
|
717
|
+
interactive=interactive,
|
|
718
|
+
release_channel=release_channel,
|
|
719
|
+
)
|
|
786
720
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
721
|
+
# 3. If no existing application found, or we performed a drop before the upgrade, we proceed to create
|
|
722
|
+
if create_or_upgrade_result is None:
|
|
723
|
+
create_or_upgrade_result = self._create_app(
|
|
724
|
+
stage_fqn=stage_fqn,
|
|
725
|
+
install_method=install_method,
|
|
726
|
+
event_sharing=event_sharing,
|
|
727
|
+
package=package,
|
|
728
|
+
release_channel=release_channel,
|
|
729
|
+
)
|
|
792
730
|
|
|
793
|
-
|
|
794
|
-
self.execute_post_deploy_hooks()
|
|
731
|
+
print_messages(self.console, create_or_upgrade_result)
|
|
795
732
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
733
|
+
events_definitions = get_snowflake_facade().get_event_definitions(
|
|
734
|
+
self.name, self.role
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
events_to_share = event_sharing.events_to_share(events_definitions)
|
|
738
|
+
if events_to_share is not None:
|
|
739
|
+
get_snowflake_facade().share_telemetry_events(
|
|
740
|
+
self.name, events_to_share, self.role
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
# hooks always executed after a create or upgrade
|
|
744
|
+
self.execute_post_deploy_hooks()
|
|
803
745
|
|
|
804
746
|
def execute_post_deploy_hooks(self):
|
|
805
747
|
execute_post_deploy_hooks(
|
|
806
|
-
console=self.
|
|
748
|
+
console=self.console,
|
|
807
749
|
project_root=self.project_root,
|
|
808
750
|
post_deploy_hooks=self.post_deploy_hooks,
|
|
809
751
|
deployed_object_type="application",
|
|
@@ -827,69 +769,59 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
827
769
|
)
|
|
828
770
|
)
|
|
829
771
|
|
|
830
|
-
def get_existing_app_info(self) -> Optional[dict]:
|
|
831
|
-
"""
|
|
832
|
-
Check for an existing application object by the same name as in project definition, in account.
|
|
833
|
-
It executes a 'show applications like' query and returns the result as single row, if one exists.
|
|
834
|
-
"""
|
|
835
|
-
sql_executor = get_sql_executor()
|
|
836
|
-
with sql_executor.use_role(self.role):
|
|
837
|
-
return sql_executor.show_specific_object(
|
|
838
|
-
"applications", self.name, name_col=NAME_COL
|
|
839
|
-
)
|
|
840
|
-
|
|
841
772
|
def drop_application_before_upgrade(
|
|
842
773
|
self,
|
|
843
774
|
policy: PolicyBase,
|
|
844
775
|
interactive: bool,
|
|
845
776
|
cascade: bool = False,
|
|
846
777
|
):
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
778
|
+
sql_executor = get_sql_executor()
|
|
779
|
+
with sql_executor.use_role(self.role):
|
|
780
|
+
if cascade:
|
|
781
|
+
try:
|
|
782
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
783
|
+
application_objects_str = _application_objects_to_str(
|
|
784
|
+
application_objects
|
|
785
|
+
)
|
|
786
|
+
self.console.message(
|
|
787
|
+
f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
|
|
788
|
+
)
|
|
789
|
+
except ProgrammingError as err:
|
|
790
|
+
if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
791
|
+
generic_sql_error_handler(err)
|
|
792
|
+
self.console.warning(
|
|
793
|
+
"The application owns other objects but they could not be determined."
|
|
854
794
|
)
|
|
855
|
-
|
|
856
|
-
|
|
795
|
+
user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
|
|
796
|
+
else:
|
|
797
|
+
user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
|
|
798
|
+
|
|
799
|
+
if not policy.should_proceed(user_prompt):
|
|
800
|
+
if interactive:
|
|
801
|
+
self.console.message("Not upgrading the application object.")
|
|
802
|
+
raise typer.Exit(0)
|
|
803
|
+
else:
|
|
804
|
+
self.console.message(
|
|
805
|
+
"Cannot upgrade the application object non-interactively without --force."
|
|
857
806
|
)
|
|
807
|
+
raise typer.Exit(1)
|
|
808
|
+
try:
|
|
809
|
+
cascade_msg = " (cascade)" if cascade else ""
|
|
810
|
+
self.console.step(
|
|
811
|
+
f"Dropping application object {self.name}{cascade_msg}."
|
|
812
|
+
)
|
|
813
|
+
cascade_sql = " cascade" if cascade else ""
|
|
814
|
+
sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
|
|
858
815
|
except ProgrammingError as err:
|
|
859
|
-
if err.errno
|
|
816
|
+
if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
|
|
817
|
+
# We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
|
|
818
|
+
return self.drop_application_before_upgrade(
|
|
819
|
+
policy=policy,
|
|
820
|
+
interactive=interactive,
|
|
821
|
+
cascade=True,
|
|
822
|
+
)
|
|
823
|
+
else:
|
|
860
824
|
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
825
|
|
|
894
826
|
def get_events(
|
|
895
827
|
self,
|
|
@@ -1043,6 +975,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
1043
975
|
except KeyboardInterrupt:
|
|
1044
976
|
return
|
|
1045
977
|
|
|
978
|
+
@span("get_snowsight_url_for_app")
|
|
1046
979
|
def get_snowsight_url(self) -> str:
|
|
1047
980
|
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
1048
981
|
name = identifier_for_url(self.name)
|