snowflake-cli-labs 2.5.0rc3__py3-none-any.whl → 2.6.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/api/cli_global_context.py +31 -3
- snowflake/cli/api/commands/decorators.py +21 -6
- snowflake/cli/api/commands/flags.py +60 -51
- snowflake/cli/api/commands/snow_typer.py +24 -0
- snowflake/cli/api/commands/typer_pre_execute.py +26 -0
- snowflake/cli/api/console/abc.py +8 -0
- snowflake/cli/api/console/console.py +29 -4
- snowflake/cli/api/constants.py +3 -0
- snowflake/cli/api/project/definition.py +17 -35
- snowflake/cli/api/project/definition_manager.py +22 -19
- snowflake/cli/api/project/errors.py +9 -6
- snowflake/cli/api/project/schemas/identifier_model.py +1 -1
- snowflake/cli/api/project/schemas/native_app/application.py +15 -3
- snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
- snowflake/cli/api/project/schemas/project_definition.py +37 -6
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
- snowflake/cli/api/project/schemas/updatable_model.py +2 -6
- snowflake/cli/api/rest_api.py +113 -0
- snowflake/cli/api/sanitizers.py +43 -0
- snowflake/cli/api/sql_execution.py +7 -0
- snowflake/cli/api/utils/definition_rendering.py +95 -25
- snowflake/cli/api/utils/models.py +31 -26
- snowflake/cli/api/utils/rendering.py +24 -3
- snowflake/cli/app/cli_app.py +2 -0
- snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
- snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
- snowflake/cli/app/dev/docs/generator.py +8 -67
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
- snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
- snowflake/cli/app/dev/docs/template_utils.py +23 -0
- snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
- snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
- snowflake/cli/app/loggers.py +25 -0
- snowflake/cli/app/printing.py +7 -5
- snowflake/cli/app/telemetry.py +11 -0
- snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
- snowflake/cli/plugins/nativeapp/commands.py +23 -12
- snowflake/cli/plugins/nativeapp/constants.py +2 -0
- snowflake/cli/plugins/nativeapp/errno.py +15 -0
- snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
- snowflake/cli/plugins/nativeapp/init.py +5 -0
- snowflake/cli/plugins/nativeapp/manager.py +101 -103
- snowflake/cli/plugins/nativeapp/project_model.py +181 -0
- snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
- snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
- snowflake/cli/plugins/nativeapp/utils.py +2 -2
- snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
- snowflake/cli/plugins/object/commands.py +70 -4
- snowflake/cli/plugins/object/manager.py +44 -3
- snowflake/cli/plugins/snowpark/commands.py +2 -2
- snowflake/cli/plugins/sql/commands.py +2 -10
- snowflake/cli/plugins/sql/manager.py +4 -2
- snowflake/cli/plugins/stage/commands.py +23 -4
- snowflake/cli/plugins/stage/diff.py +81 -51
- snowflake/cli/plugins/stage/manager.py +2 -1
- snowflake/cli/plugins/streamlit/commands.py +2 -1
- snowflake/cli/plugins/streamlit/manager.py +6 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/METADATA +15 -9
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/RECORD +67 -56
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/WHEEL +1 -1
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -28,10 +28,13 @@ from snowflake.cli.api.project.util import (
|
|
|
28
28
|
unquote_identifier,
|
|
29
29
|
)
|
|
30
30
|
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
31
|
+
from snowflake.cli.api.utils.rendering import snowflake_sql_jinja_render
|
|
31
32
|
from snowflake.cli.plugins.nativeapp.artifacts import BundleMap
|
|
32
33
|
from snowflake.cli.plugins.nativeapp.constants import (
|
|
33
34
|
ALLOWED_SPECIAL_COMMENTS,
|
|
34
35
|
COMMENT_COL,
|
|
36
|
+
ERROR_MESSAGE_093079,
|
|
37
|
+
ERROR_MESSAGE_093128,
|
|
35
38
|
LOOSE_FILES_MAGIC_VERSION,
|
|
36
39
|
PATCH_COL,
|
|
37
40
|
SPECIAL_COMMENT,
|
|
@@ -48,99 +51,117 @@ from snowflake.cli.plugins.nativeapp.manager import (
|
|
|
48
51
|
generic_sql_error_handler,
|
|
49
52
|
)
|
|
50
53
|
from snowflake.cli.plugins.nativeapp.policy import PolicyBase
|
|
51
|
-
from snowflake.cli.plugins.
|
|
54
|
+
from snowflake.cli.plugins.nativeapp.project_model import (
|
|
55
|
+
NativeAppProjectModel,
|
|
56
|
+
)
|
|
52
57
|
from snowflake.cli.plugins.stage.manager import StageManager
|
|
53
58
|
from snowflake.connector import ProgrammingError
|
|
54
59
|
from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
# Reasons why an `alter application ... upgrade` might fail
|
|
62
|
+
UPGRADE_RESTRICTION_CODES = {
|
|
63
|
+
93044, # Cannot upgrade dev mode application from loose stage files to version
|
|
64
|
+
93045, # Cannot upgrade dev mode application from version to loose stage files
|
|
65
|
+
93046, # Operation only permitted on dev mode application
|
|
66
|
+
93055, # Operation not supported on dev mode application
|
|
67
|
+
93079, # App package access lost
|
|
68
|
+
}
|
|
57
69
|
|
|
58
70
|
|
|
59
|
-
class
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
class SameAccountInstallMethod:
|
|
72
|
+
_requires_created_by_cli: bool
|
|
73
|
+
_from_release_directive: bool
|
|
74
|
+
version: Optional[str]
|
|
75
|
+
patch: Optional[int]
|
|
62
76
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
requires_created_by_cli: bool,
|
|
80
|
+
version: Optional[str] = None,
|
|
81
|
+
patch: Optional[int] = None,
|
|
82
|
+
from_release_directive: bool = False,
|
|
83
|
+
):
|
|
84
|
+
self._requires_created_by_cli = requires_created_by_cli
|
|
85
|
+
self.version = version
|
|
86
|
+
self.patch = patch
|
|
87
|
+
self._from_release_directive = from_release_directive
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
except ProgrammingError as err:
|
|
74
|
-
generic_sql_error_handler(
|
|
75
|
-
err=err, role=self.app_role, warehouse=self.application_warehouse
|
|
76
|
-
)
|
|
89
|
+
@classmethod
|
|
90
|
+
def unversioned_dev(cls):
|
|
91
|
+
"""aka. stage dev aka loose files"""
|
|
92
|
+
return cls(True)
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
@classmethod
|
|
95
|
+
def versioned_dev(cls, version: str, patch: Optional[int] = None):
|
|
96
|
+
return cls(False, version, patch)
|
|
80
97
|
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
@classmethod
|
|
99
|
+
def release_directive(cls):
|
|
100
|
+
return cls(False, from_release_directive=True)
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
):
|
|
88
|
-
raise ApplicationAlreadyExistsError(self.app_name)
|
|
102
|
+
@property
|
|
103
|
+
def is_dev_mode(self) -> bool:
|
|
104
|
+
return not self._from_release_directive
|
|
89
105
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
106
|
+
def using_clause(self, app: NativeAppProjectModel) -> str:
|
|
107
|
+
if self._from_release_directive:
|
|
108
|
+
return ""
|
|
94
109
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
self._execute_query(
|
|
99
|
-
f"alter application {self.app_name} upgrade using @{self.stage_fqn}"
|
|
100
|
-
)
|
|
110
|
+
if self.version:
|
|
111
|
+
patch_clause = f"patch {self.patch}" if self.patch else ""
|
|
112
|
+
return f"using version {self.version} {patch_clause}"
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
|
|
105
|
-
)
|
|
114
|
+
stage_name = StageManager.quote_stage_name(app.stage_fqn)
|
|
115
|
+
return f"using {stage_name}"
|
|
106
116
|
|
|
107
|
-
|
|
117
|
+
def ensure_app_usable(self, app: NativeAppProjectModel, show_app_row: dict):
|
|
118
|
+
"""Raise an exception if we cannot proceed with install given the pre-existing application object"""
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
if self._requires_created_by_cli:
|
|
121
|
+
if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS or (
|
|
122
|
+
show_app_row[VERSION_COL] != LOOSE_FILES_MAGIC_VERSION
|
|
123
|
+
):
|
|
124
|
+
# this application object was not created by this tooling
|
|
125
|
+
raise ApplicationAlreadyExistsError(app.app_name)
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
# expected owner
|
|
128
|
+
ensure_correct_owner(row=show_app_row, role=app.app_role, obj_name=app.app_name)
|
|
114
129
|
|
|
115
|
-
if self.app_role != self.package_role:
|
|
116
|
-
with self.use_role(new_role=self.package_role):
|
|
117
|
-
self._execute_queries(
|
|
118
|
-
dedent(
|
|
119
|
-
f"""\
|
|
120
|
-
grant install, develop on application package {self.package_name} to role {self.app_role};
|
|
121
|
-
grant usage on schema {self.package_name}.{self.stage_schema} to role {self.app_role};
|
|
122
|
-
grant read on stage {self.stage_fqn} to role {self.app_role};
|
|
123
|
-
"""
|
|
124
|
-
)
|
|
125
|
-
)
|
|
126
130
|
|
|
127
|
-
|
|
131
|
+
class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
132
|
+
def __init__(self, project_definition: NativeApp, project_root: Path):
|
|
133
|
+
super().__init__(project_definition, project_root)
|
|
128
134
|
|
|
135
|
+
def _execute_sql_script(self, sql_script_path):
|
|
136
|
+
"""
|
|
137
|
+
Executing the SQL script in the provided file path after expanding template variables.
|
|
138
|
+
"use warehouse" and "use database" will be executed first if they are set in definition file or in the current connection.
|
|
139
|
+
"""
|
|
140
|
+
with open(sql_script_path) as f:
|
|
141
|
+
sql_script = f.read()
|
|
129
142
|
try:
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
debug_mode = {self.debug_mode}
|
|
137
|
-
comment = {SPECIAL_COMMENT}
|
|
138
|
-
"""
|
|
139
|
-
)
|
|
140
|
-
)
|
|
143
|
+
if self.application_warehouse:
|
|
144
|
+
self._execute_query(f"use warehouse {self.application_warehouse}")
|
|
145
|
+
if self._conn.database:
|
|
146
|
+
self._execute_query(f"use database {self._conn.database}")
|
|
147
|
+
sql_script = snowflake_sql_jinja_render(content=sql_script)
|
|
148
|
+
self._execute_queries(sql_script)
|
|
141
149
|
except ProgrammingError as err:
|
|
142
150
|
generic_sql_error_handler(err)
|
|
143
151
|
|
|
152
|
+
def _execute_post_deploy_hooks(self):
|
|
153
|
+
post_deploy_script_hooks = self.app_post_deploy_hooks
|
|
154
|
+
if post_deploy_script_hooks:
|
|
155
|
+
with cc.phase("Executing application post-deploy actions"):
|
|
156
|
+
for hook in post_deploy_script_hooks:
|
|
157
|
+
if hook.sql_script:
|
|
158
|
+
cc.step(f"Executing SQL script: {hook.sql_script}")
|
|
159
|
+
self._execute_sql_script(hook.sql_script)
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"Unsupported application post-deploy hook type: {hook}"
|
|
163
|
+
)
|
|
164
|
+
|
|
144
165
|
def get_all_existing_versions(self) -> SnowflakeCursor:
|
|
145
166
|
"""
|
|
146
167
|
Get all existing versions, if defined, for an application package.
|
|
@@ -186,11 +207,31 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
186
207
|
generic_sql_error_handler(err=err, role=self.package_role)
|
|
187
208
|
return None
|
|
188
209
|
|
|
189
|
-
def drop_application_before_upgrade(
|
|
210
|
+
def drop_application_before_upgrade(
|
|
211
|
+
self, policy: PolicyBase, is_interactive: bool, cascade: bool = False
|
|
212
|
+
):
|
|
190
213
|
"""
|
|
191
214
|
This method will attempt to drop an application object if a previous upgrade fails.
|
|
192
215
|
"""
|
|
193
|
-
|
|
216
|
+
if cascade:
|
|
217
|
+
try:
|
|
218
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
219
|
+
application_objects_str = self._application_objects_to_str(
|
|
220
|
+
application_objects
|
|
221
|
+
)
|
|
222
|
+
cc.message(
|
|
223
|
+
f"The following objects are owned by application {self.app_name} and need to be dropped:\n{application_objects_str}"
|
|
224
|
+
)
|
|
225
|
+
except ProgrammingError as err:
|
|
226
|
+
if err.errno != 93079 and ERROR_MESSAGE_093079 not in err.msg:
|
|
227
|
+
generic_sql_error_handler(err)
|
|
228
|
+
cc.warning(
|
|
229
|
+
"The application owns other objects but they could not be determined."
|
|
230
|
+
)
|
|
231
|
+
user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
|
|
232
|
+
else:
|
|
233
|
+
user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
|
|
234
|
+
|
|
194
235
|
if not policy.should_proceed(user_prompt):
|
|
195
236
|
if is_interactive:
|
|
196
237
|
cc.message("Not upgrading the application object.")
|
|
@@ -201,21 +242,23 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
201
242
|
)
|
|
202
243
|
raise typer.Exit(1)
|
|
203
244
|
try:
|
|
204
|
-
|
|
245
|
+
cascade_sql = " cascade" if cascade else ""
|
|
246
|
+
self._execute_query(f"drop application {self.app_name}{cascade_sql}")
|
|
205
247
|
except ProgrammingError as err:
|
|
206
|
-
|
|
248
|
+
if (err.errno == 93128 or ERROR_MESSAGE_093128 in err.msg) and not cascade:
|
|
249
|
+
# We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
|
|
250
|
+
return self.drop_application_before_upgrade(
|
|
251
|
+
policy, is_interactive, cascade=True
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
generic_sql_error_handler(err)
|
|
207
255
|
|
|
208
|
-
def
|
|
256
|
+
def create_or_upgrade_app(
|
|
209
257
|
self,
|
|
210
258
|
policy: PolicyBase,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
patch: Optional[int] = None,
|
|
259
|
+
install_method: SameAccountInstallMethod,
|
|
260
|
+
is_interactive: bool = False,
|
|
214
261
|
):
|
|
215
|
-
|
|
216
|
-
patch_clause = f"patch {patch}" if patch else ""
|
|
217
|
-
using_clause = f"using version {version} {patch_clause}" if version else ""
|
|
218
|
-
|
|
219
262
|
with self.use_role(self.app_role):
|
|
220
263
|
|
|
221
264
|
# 1. Need to use a warehouse to create an application object
|
|
@@ -233,23 +276,25 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
233
276
|
# 3. If existing application is found, perform a few validations and upgrade the application object.
|
|
234
277
|
if show_app_row:
|
|
235
278
|
|
|
236
|
-
|
|
237
|
-
# Check for the right owner
|
|
238
|
-
ensure_correct_owner(
|
|
239
|
-
row=show_app_row, role=self.app_role, obj_name=self.app_name
|
|
240
|
-
)
|
|
279
|
+
install_method.ensure_app_usable(self._na_project, show_app_row)
|
|
241
280
|
|
|
242
281
|
# If all the above checks are in order, proceed to upgrade
|
|
243
282
|
try:
|
|
283
|
+
cc.step(f"Upgrading existing application object {self.app_name}.")
|
|
284
|
+
using_clause = install_method.using_clause(self._na_project)
|
|
244
285
|
self._execute_query(
|
|
245
286
|
f"alter application {self.app_name} upgrade {using_clause}"
|
|
246
287
|
)
|
|
247
288
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
self.
|
|
251
|
-
|
|
252
|
-
|
|
289
|
+
if install_method.is_dev_mode:
|
|
290
|
+
# if debug_mode is present (controlled), ensure it is up-to-date
|
|
291
|
+
if self.debug_mode is not None:
|
|
292
|
+
self._execute_query(
|
|
293
|
+
f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# hooks always executed after a create or upgrade
|
|
297
|
+
self._execute_post_deploy_hooks()
|
|
253
298
|
return
|
|
254
299
|
|
|
255
300
|
except ProgrammingError as err:
|
|
@@ -263,31 +308,41 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
263
308
|
cc.step(f"Creating new application object {self.app_name} in account.")
|
|
264
309
|
|
|
265
310
|
if self.app_role != self.package_role:
|
|
266
|
-
with self.use_role(
|
|
311
|
+
with self.use_role(self.package_role):
|
|
267
312
|
self._execute_query(
|
|
268
|
-
f"grant install on application package {self.package_name} to role {self.app_role}"
|
|
313
|
+
f"grant install, develop on application package {self.package_name} to role {self.app_role}"
|
|
314
|
+
)
|
|
315
|
+
self._execute_query(
|
|
316
|
+
f"grant usage on schema {self.package_name}.{self.stage_schema} to role {self.app_role}"
|
|
317
|
+
)
|
|
318
|
+
self._execute_query(
|
|
319
|
+
f"grant read on stage {self.stage_fqn} to role {self.app_role}"
|
|
269
320
|
)
|
|
270
|
-
if version:
|
|
271
|
-
self._execute_query(
|
|
272
|
-
f"grant develop on application package {self.package_name} to role {self.app_role}"
|
|
273
|
-
)
|
|
274
321
|
|
|
275
322
|
try:
|
|
323
|
+
# by default, applications are created in debug mode when possible;
|
|
324
|
+
# this can be overridden in the project definition
|
|
325
|
+
debug_mode_clause = ""
|
|
326
|
+
if install_method.is_dev_mode:
|
|
327
|
+
initial_debug_mode = (
|
|
328
|
+
self.debug_mode if self.debug_mode is not None else True
|
|
329
|
+
)
|
|
330
|
+
debug_mode_clause = f"debug_mode = {initial_debug_mode}"
|
|
331
|
+
|
|
332
|
+
using_clause = install_method.using_clause(self._na_project)
|
|
276
333
|
self._execute_query(
|
|
277
334
|
dedent(
|
|
278
335
|
f"""\
|
|
279
336
|
create application {self.app_name}
|
|
280
|
-
from application package {self.package_name} {using_clause}
|
|
337
|
+
from application package {self.package_name} {using_clause} {debug_mode_clause}
|
|
281
338
|
comment = {SPECIAL_COMMENT}
|
|
282
339
|
"""
|
|
283
340
|
)
|
|
284
341
|
)
|
|
285
342
|
|
|
286
|
-
#
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
|
|
290
|
-
)
|
|
343
|
+
# hooks always executed after a create or upgrade
|
|
344
|
+
self._execute_post_deploy_hooks()
|
|
345
|
+
|
|
291
346
|
except ProgrammingError as err:
|
|
292
347
|
generic_sql_error_handler(err)
|
|
293
348
|
|
|
@@ -303,12 +358,21 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
303
358
|
*args,
|
|
304
359
|
**kwargs,
|
|
305
360
|
):
|
|
306
|
-
"""
|
|
361
|
+
"""
|
|
362
|
+
Create or upgrade the application object using the given strategy
|
|
363
|
+
(unversioned dev, versioned dev, or same-account release directive).
|
|
364
|
+
"""
|
|
307
365
|
|
|
366
|
+
# same-account release directive
|
|
308
367
|
if from_release_directive:
|
|
309
|
-
self.
|
|
368
|
+
self.create_or_upgrade_app(
|
|
369
|
+
policy=policy,
|
|
370
|
+
is_interactive=is_interactive,
|
|
371
|
+
install_method=SameAccountInstallMethod.release_directive(),
|
|
372
|
+
)
|
|
310
373
|
return
|
|
311
374
|
|
|
375
|
+
# versioned dev
|
|
312
376
|
if version:
|
|
313
377
|
try:
|
|
314
378
|
version_exists = self.get_existing_version_info(version)
|
|
@@ -321,15 +385,19 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
321
385
|
f"Application package {self.package_name} does not exist. Use 'snow app version create' to first create an application package and then define a version in it."
|
|
322
386
|
)
|
|
323
387
|
|
|
324
|
-
self.
|
|
388
|
+
self.create_or_upgrade_app(
|
|
325
389
|
policy=policy,
|
|
326
|
-
|
|
327
|
-
patch=patch,
|
|
390
|
+
install_method=SameAccountInstallMethod.versioned_dev(version, patch),
|
|
328
391
|
is_interactive=is_interactive,
|
|
329
392
|
)
|
|
330
393
|
return
|
|
331
394
|
|
|
332
|
-
|
|
395
|
+
# unversioned dev
|
|
396
|
+
self.deploy(
|
|
333
397
|
bundle_map=bundle_map, prune=True, recursive=True, validate=validate
|
|
334
398
|
)
|
|
335
|
-
self.
|
|
399
|
+
self.create_or_upgrade_app(
|
|
400
|
+
policy=policy,
|
|
401
|
+
is_interactive=is_interactive,
|
|
402
|
+
install_method=SameAccountInstallMethod.unversioned_dev(),
|
|
403
|
+
)
|
|
@@ -28,11 +28,11 @@ from snowflake.cli.plugins.nativeapp.constants import (
|
|
|
28
28
|
INTERNAL_DISTRIBUTION,
|
|
29
29
|
OWNER_COL,
|
|
30
30
|
)
|
|
31
|
+
from snowflake.cli.plugins.nativeapp.errno import APPLICATION_NO_LONGER_AVAILABLE
|
|
31
32
|
from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
32
33
|
CouldNotDropApplicationPackageWithVersions,
|
|
33
34
|
)
|
|
34
35
|
from snowflake.cli.plugins.nativeapp.manager import (
|
|
35
|
-
ApplicationOwnedObject,
|
|
36
36
|
NativeAppCommandProcessor,
|
|
37
37
|
NativeAppManager,
|
|
38
38
|
ensure_correct_owner,
|
|
@@ -40,6 +40,7 @@ from snowflake.cli.plugins.nativeapp.manager import (
|
|
|
40
40
|
from snowflake.cli.plugins.nativeapp.utils import (
|
|
41
41
|
needs_confirmation,
|
|
42
42
|
)
|
|
43
|
+
from snowflake.connector import ProgrammingError
|
|
43
44
|
from snowflake.connector.cursor import DictCursor
|
|
44
45
|
|
|
45
46
|
|
|
@@ -65,20 +66,6 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
65
66
|
|
|
66
67
|
cc.message(f"Dropped {object_type} {object_name} successfully.")
|
|
67
68
|
|
|
68
|
-
def _application_objects_to_str(
|
|
69
|
-
self, application_objects: ApplicationOwnedObject
|
|
70
|
-
) -> str:
|
|
71
|
-
"""
|
|
72
|
-
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
73
|
-
(COMPUTE_POOL) POOL_NAME
|
|
74
|
-
(DATABASE) DB_NAME
|
|
75
|
-
(SCHEMA) DB_NAME.PUBLIC
|
|
76
|
-
...
|
|
77
|
-
"""
|
|
78
|
-
return "\n".join(
|
|
79
|
-
[f"({obj['type']}) {obj['name']}" for obj in application_objects]
|
|
80
|
-
)
|
|
81
|
-
|
|
82
69
|
def drop_application(
|
|
83
70
|
self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
|
|
84
71
|
):
|
|
@@ -88,7 +75,7 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
88
75
|
|
|
89
76
|
needs_confirm = True
|
|
90
77
|
|
|
91
|
-
# 1. If existing application
|
|
78
|
+
# 1. If existing application is not found, exit gracefully
|
|
92
79
|
show_obj_row = self.get_existing_app_info()
|
|
93
80
|
if show_obj_row is None:
|
|
94
81
|
cc.warning(
|
|
@@ -103,64 +90,102 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
103
90
|
|
|
104
91
|
# 3. Check if created by the Snowflake CLI
|
|
105
92
|
row_comment = show_obj_row[COMMENT_COL]
|
|
106
|
-
if row_comment in ALLOWED_SPECIAL_COMMENTS
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Are you sure you want to drop it?
|
|
124
|
-
"""
|
|
125
|
-
)
|
|
93
|
+
if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
|
|
94
|
+
needs_confirm, auto_yes
|
|
95
|
+
):
|
|
96
|
+
should_drop_object = typer.confirm(
|
|
97
|
+
dedent(
|
|
98
|
+
f"""\
|
|
99
|
+
Application object {self.app_name} was not created by Snowflake CLI.
|
|
100
|
+
Application object details:
|
|
101
|
+
Name: {self.app_name}
|
|
102
|
+
Created on: {show_obj_row["created_on"]}
|
|
103
|
+
Source: {show_obj_row["source"]}
|
|
104
|
+
Owner: {show_obj_row[OWNER_COL]}
|
|
105
|
+
Comment: {show_obj_row[COMMENT_COL]}
|
|
106
|
+
Version: {show_obj_row["version"]}
|
|
107
|
+
Patch: {show_obj_row["patch"]}
|
|
108
|
+
Are you sure you want to drop it?
|
|
109
|
+
"""
|
|
126
110
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
111
|
+
)
|
|
112
|
+
if not should_drop_object:
|
|
113
|
+
cc.message(f"Did not drop application object {self.app_name}.")
|
|
114
|
+
# The user desires to keep the app, therefore we can't proceed since it would
|
|
115
|
+
# leave behind an orphan app when we get to dropping the package
|
|
116
|
+
raise typer.Abort()
|
|
130
117
|
|
|
131
118
|
# 4. Check for application objects owned by the application
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
119
|
+
# This query will fail if the application package has already been dropped, so handle this case gracefully
|
|
120
|
+
has_objects_to_drop = False
|
|
121
|
+
message_prefix = ""
|
|
122
|
+
cascade_true_message = ""
|
|
123
|
+
cascade_false_message = ""
|
|
124
|
+
interactive_prompt = ""
|
|
125
|
+
non_interactive_abort = ""
|
|
126
|
+
try:
|
|
127
|
+
if application_objects := self.get_objects_owned_by_application():
|
|
128
|
+
has_objects_to_drop = True
|
|
129
|
+
message_prefix = (
|
|
130
|
+
f"The following objects are owned by application {self.app_name}"
|
|
131
|
+
)
|
|
132
|
+
cascade_true_message = f"{message_prefix} and will be dropped:"
|
|
133
|
+
cascade_false_message = f"{message_prefix} and will NOT be dropped:"
|
|
134
|
+
interactive_prompt = "Would you like to drop these objects in addition to the application? [y/n/ABORT]"
|
|
135
|
+
non_interactive_abort = "Re-run teardown again with --cascade or --no-cascade to specify whether these objects should be dropped along with the application"
|
|
136
|
+
except ProgrammingError as e:
|
|
137
|
+
if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
138
|
+
raise
|
|
139
|
+
application_objects = []
|
|
140
|
+
message_prefix = f"Could not determine which objects are owned by application {self.app_name}"
|
|
141
|
+
has_objects_to_drop = True # potentially, but we don't know what they are
|
|
142
|
+
cascade_true_message = (
|
|
143
|
+
f"{message_prefix}, an unknown number of objects will be dropped."
|
|
136
144
|
)
|
|
145
|
+
cascade_false_message = f"{message_prefix}, they will NOT be dropped."
|
|
146
|
+
interactive_prompt = f"Would you like to drop an unknown set of objects in addition to the application? [y/n/ABORT]"
|
|
147
|
+
non_interactive_abort = f"Re-run teardown again with --cascade or --no-cascade to specify whether any objects should be dropped along with the application."
|
|
148
|
+
|
|
149
|
+
if has_objects_to_drop:
|
|
137
150
|
if cascade is True:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
151
|
+
# If the user explicitly passed the --cascade flag
|
|
152
|
+
cc.message(cascade_true_message)
|
|
153
|
+
with cc.indented():
|
|
154
|
+
for obj in application_objects:
|
|
155
|
+
cc.message(self._application_object_to_str(obj))
|
|
141
156
|
elif cascade is False:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
157
|
+
# If the user explicitly passed the --no-cascade flag
|
|
158
|
+
cc.message(cascade_false_message)
|
|
159
|
+
with cc.indented():
|
|
160
|
+
for obj in application_objects:
|
|
161
|
+
cc.message(self._application_object_to_str(obj))
|
|
145
162
|
elif interactive:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
# If the user didn't pass any cascade flag and the session is interactive
|
|
164
|
+
cc.message(message_prefix)
|
|
165
|
+
with cc.indented():
|
|
166
|
+
for obj in application_objects:
|
|
167
|
+
cc.message(self._application_object_to_str(obj))
|
|
168
|
+
user_response = typer.prompt(
|
|
169
|
+
interactive_prompt,
|
|
170
|
+
show_default=False,
|
|
171
|
+
default="ABORT",
|
|
172
|
+
).lower()
|
|
173
|
+
if user_response in ["y", "yes"]:
|
|
174
|
+
cascade = True
|
|
175
|
+
elif user_response in ["n", "no"]:
|
|
176
|
+
cascade = False
|
|
177
|
+
else:
|
|
178
|
+
raise typer.Abort()
|
|
158
179
|
else:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
)
|
|
180
|
+
# Else abort since we don't know what to do and can't ask the user
|
|
181
|
+
cc.message(message_prefix)
|
|
182
|
+
with cc.indented():
|
|
183
|
+
for obj in application_objects:
|
|
184
|
+
cc.message(self._application_object_to_str(obj))
|
|
185
|
+
cc.message(non_interactive_abort)
|
|
162
186
|
raise typer.Abort()
|
|
163
187
|
elif cascade is None:
|
|
188
|
+
# If there's nothing to drop, set cascade to an explicit False value
|
|
164
189
|
cascade = False
|
|
165
190
|
|
|
166
191
|
# 5. All validations have passed, drop object
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import os
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from sys import stdin, stdout
|
|
20
|
-
from typing import
|
|
20
|
+
from typing import Iterable, Optional, Union
|
|
21
21
|
|
|
22
22
|
from click import ClickException
|
|
23
23
|
|
|
@@ -85,7 +85,7 @@ def shallow_git_clone(url: Union[str, os.PathLike], to_path: Union[str, os.PathL
|
|
|
85
85
|
return repo
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def verify_no_directories(paths_to_sync:
|
|
88
|
+
def verify_no_directories(paths_to_sync: Iterable[Path]):
|
|
89
89
|
for path in paths_to_sync:
|
|
90
90
|
if path.is_dir():
|
|
91
91
|
raise ClickException(
|
|
@@ -89,7 +89,7 @@ def create(
|
|
|
89
89
|
git_policy = AllowAlwaysPolicy()
|
|
90
90
|
|
|
91
91
|
processor = NativeAppVersionCreateProcessor(
|
|
92
|
-
project_definition=cli_context.project_definition,
|
|
92
|
+
project_definition=cli_context.project_definition.native_app,
|
|
93
93
|
project_root=cli_context.project_root,
|
|
94
94
|
)
|
|
95
95
|
|
|
@@ -115,7 +115,7 @@ def version_list(
|
|
|
115
115
|
Lists all versions defined in an application package.
|
|
116
116
|
"""
|
|
117
117
|
processor = NativeAppRunProcessor(
|
|
118
|
-
project_definition=cli_context.project_definition,
|
|
118
|
+
project_definition=cli_context.project_definition.native_app,
|
|
119
119
|
project_root=cli_context.project_root,
|
|
120
120
|
)
|
|
121
121
|
cursor = processor.get_all_existing_versions()
|
|
@@ -147,7 +147,7 @@ def drop(
|
|
|
147
147
|
policy = DenyAlwaysPolicy()
|
|
148
148
|
|
|
149
149
|
processor = NativeAppVersionDropProcessor(
|
|
150
|
-
project_definition=cli_context.project_definition,
|
|
150
|
+
project_definition=cli_context.project_definition.native_app,
|
|
151
151
|
project_root=cli_context.project_root,
|
|
152
152
|
)
|
|
153
153
|
processor.process(version, policy, is_interactive)
|