snowflake-cli-labs 2.6.0rc0__py3-none-any.whl → 2.7.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 +9 -0
- snowflake/cli/api/commands/decorators.py +9 -4
- snowflake/cli/api/commands/execution_metadata.py +40 -0
- snowflake/cli/api/commands/flags.py +45 -36
- snowflake/cli/api/commands/project_initialisation.py +5 -2
- snowflake/cli/api/commands/snow_typer.py +20 -9
- snowflake/cli/api/config.py +4 -0
- snowflake/cli/api/errno.py +27 -0
- snowflake/cli/api/feature_flags.py +5 -0
- snowflake/cli/api/identifiers.py +20 -3
- snowflake/cli/api/output/types.py +9 -0
- snowflake/cli/api/project/definition_manager.py +2 -2
- snowflake/cli/api/project/project_verification.py +23 -0
- snowflake/cli/api/project/schemas/entities/application_entity.py +50 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity.py +63 -0
- snowflake/cli/api/project/schemas/entities/common.py +85 -0
- snowflake/cli/api/project/schemas/entities/entities.py +30 -0
- snowflake/cli/api/project/schemas/project_definition.py +114 -22
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +5 -4
- snowflake/cli/api/project/schemas/template.py +77 -0
- snowflake/cli/{plugins/nativeapp/errno.py → api/rendering/__init__.py} +0 -2
- snowflake/cli/api/{utils/rendering.py → rendering/jinja.py} +3 -48
- snowflake/cli/api/rendering/project_definition_templates.py +39 -0
- snowflake/cli/api/rendering/project_templates.py +97 -0
- snowflake/cli/api/rendering/sql_templates.py +56 -0
- snowflake/cli/api/rest_api.py +84 -25
- snowflake/cli/api/sql_execution.py +40 -1
- snowflake/cli/api/utils/definition_rendering.py +8 -5
- snowflake/cli/app/cli_app.py +0 -2
- snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
- snowflake/cli/app/loggers.py +10 -6
- snowflake/cli/app/printing.py +17 -7
- snowflake/cli/app/snow_connector.py +9 -1
- snowflake/cli/app/telemetry.py +41 -2
- snowflake/cli/plugins/connection/commands.py +13 -3
- snowflake/cli/plugins/connection/util.py +73 -18
- snowflake/cli/plugins/cortex/commands.py +2 -1
- snowflake/cli/plugins/git/commands.py +20 -4
- snowflake/cli/plugins/git/manager.py +44 -20
- snowflake/cli/plugins/init/__init__.py +13 -0
- snowflake/cli/plugins/init/commands.py +242 -0
- snowflake/cli/plugins/init/plugin_spec.py +30 -0
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
- snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
- snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
- snowflake/cli/plugins/nativeapp/commands.py +100 -6
- snowflake/cli/plugins/nativeapp/constants.py +0 -6
- snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
- snowflake/cli/plugins/nativeapp/init.py +1 -1
- snowflake/cli/plugins/nativeapp/manager.py +114 -39
- snowflake/cli/plugins/nativeapp/project_model.py +8 -4
- snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
- snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
- snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
- snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
- snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
- snowflake/cli/plugins/object/commands.py +1 -1
- snowflake/cli/plugins/object/manager.py +2 -15
- snowflake/cli/plugins/snowpark/commands.py +34 -26
- snowflake/cli/plugins/snowpark/common.py +88 -27
- snowflake/cli/plugins/snowpark/manager.py +16 -5
- snowflake/cli/plugins/snowpark/models.py +6 -0
- snowflake/cli/plugins/sql/commands.py +3 -5
- snowflake/cli/plugins/sql/manager.py +1 -1
- snowflake/cli/plugins/stage/commands.py +2 -2
- snowflake/cli/plugins/stage/diff.py +27 -64
- snowflake/cli/plugins/stage/manager.py +290 -86
- snowflake/cli/plugins/stage/md5.py +160 -0
- snowflake/cli/plugins/streamlit/commands.py +20 -6
- snowflake/cli/plugins/streamlit/manager.py +46 -32
- snowflake/cli/plugins/workspace/__init__.py +13 -0
- snowflake/cli/plugins/workspace/commands.py +35 -0
- snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
- snowflake/cli/templates/default_snowpark/app/common.py +0 -15
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/METADATA +7 -6
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/RECORD +90 -69
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,30 +18,39 @@ from pathlib import Path
|
|
|
18
18
|
from textwrap import dedent
|
|
19
19
|
from typing import Optional
|
|
20
20
|
|
|
21
|
+
import jinja2
|
|
21
22
|
import typer
|
|
22
23
|
from click import UsageError
|
|
24
|
+
from snowflake.cli.api.cli_global_context import cli_context
|
|
23
25
|
from snowflake.cli.api.console import cli_console as cc
|
|
26
|
+
from snowflake.cli.api.errno import (
|
|
27
|
+
APPLICATION_NO_LONGER_AVAILABLE,
|
|
28
|
+
APPLICATION_OWNS_EXTERNAL_OBJECTS,
|
|
29
|
+
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
30
|
+
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
|
|
31
|
+
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
32
|
+
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
33
|
+
)
|
|
24
34
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
25
35
|
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
|
|
26
36
|
from snowflake.cli.api.project.util import (
|
|
27
37
|
identifier_to_show_like_pattern,
|
|
28
38
|
unquote_identifier,
|
|
29
39
|
)
|
|
40
|
+
from snowflake.cli.api.rendering.sql_templates import (
|
|
41
|
+
get_sql_cli_jinja_env,
|
|
42
|
+
)
|
|
30
43
|
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
31
|
-
from snowflake.cli.api.utils.rendering import snowflake_sql_jinja_render
|
|
32
44
|
from snowflake.cli.plugins.nativeapp.artifacts import BundleMap
|
|
33
45
|
from snowflake.cli.plugins.nativeapp.constants import (
|
|
34
46
|
ALLOWED_SPECIAL_COMMENTS,
|
|
35
47
|
COMMENT_COL,
|
|
36
|
-
ERROR_MESSAGE_093079,
|
|
37
|
-
ERROR_MESSAGE_093128,
|
|
38
|
-
LOOSE_FILES_MAGIC_VERSION,
|
|
39
48
|
PATCH_COL,
|
|
40
49
|
SPECIAL_COMMENT,
|
|
41
50
|
VERSION_COL,
|
|
42
51
|
)
|
|
43
52
|
from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
44
|
-
|
|
53
|
+
ApplicationCreatedExternallyError,
|
|
45
54
|
ApplicationPackageDoesNotExistError,
|
|
46
55
|
)
|
|
47
56
|
from snowflake.cli.plugins.nativeapp.manager import (
|
|
@@ -60,11 +69,11 @@ from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
|
60
69
|
|
|
61
70
|
# Reasons why an `alter application ... upgrade` might fail
|
|
62
71
|
UPGRADE_RESTRICTION_CODES = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
|
|
73
|
+
CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
|
|
74
|
+
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
75
|
+
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
76
|
+
APPLICATION_NO_LONGER_AVAILABLE,
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
|
|
@@ -118,11 +127,9 @@ class SameAccountInstallMethod:
|
|
|
118
127
|
"""Raise an exception if we cannot proceed with install given the pre-existing application object"""
|
|
119
128
|
|
|
120
129
|
if self._requires_created_by_cli:
|
|
121
|
-
if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS
|
|
122
|
-
show_app_row[VERSION_COL] != LOOSE_FILES_MAGIC_VERSION
|
|
123
|
-
):
|
|
130
|
+
if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS:
|
|
124
131
|
# this application object was not created by this tooling
|
|
125
|
-
raise
|
|
132
|
+
raise ApplicationCreatedExternallyError(app.app_name)
|
|
126
133
|
|
|
127
134
|
# expected owner
|
|
128
135
|
ensure_correct_owner(row=show_app_row, role=app.app_role, obj_name=app.app_name)
|
|
@@ -132,36 +139,46 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
132
139
|
def __init__(self, project_definition: NativeApp, project_root: Path):
|
|
133
140
|
super().__init__(project_definition, project_root)
|
|
134
141
|
|
|
135
|
-
def _execute_sql_script(
|
|
142
|
+
def _execute_sql_script(
|
|
143
|
+
self, script_content: str, database_name: Optional[str] = None
|
|
144
|
+
):
|
|
136
145
|
"""
|
|
137
|
-
Executing the SQL script
|
|
138
|
-
|
|
146
|
+
Executing the provided SQL script content.
|
|
147
|
+
This assumes that a relevant warehouse is already active.
|
|
148
|
+
If database_name is passed in, it will be used first.
|
|
139
149
|
"""
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
sql_script = snowflake_sql_jinja_render(content=sql_script)
|
|
148
|
-
self._execute_queries(sql_script)
|
|
149
|
-
except ProgrammingError as err:
|
|
150
|
-
generic_sql_error_handler(err)
|
|
150
|
+
try:
|
|
151
|
+
if database_name is not None:
|
|
152
|
+
self._execute_query(f"use database {database_name}")
|
|
153
|
+
|
|
154
|
+
self._execute_queries(script_content)
|
|
155
|
+
except ProgrammingError as err:
|
|
156
|
+
generic_sql_error_handler(err)
|
|
151
157
|
|
|
152
158
|
def _execute_post_deploy_hooks(self):
|
|
153
159
|
post_deploy_script_hooks = self.app_post_deploy_hooks
|
|
154
160
|
if post_deploy_script_hooks:
|
|
155
161
|
with cc.phase("Executing application post-deploy actions"):
|
|
162
|
+
sql_scripts_paths = []
|
|
156
163
|
for hook in post_deploy_script_hooks:
|
|
157
164
|
if hook.sql_script:
|
|
158
|
-
|
|
159
|
-
self._execute_sql_script(hook.sql_script)
|
|
165
|
+
sql_scripts_paths.append(hook.sql_script)
|
|
160
166
|
else:
|
|
161
167
|
raise ValueError(
|
|
162
168
|
f"Unsupported application post-deploy hook type: {hook}"
|
|
163
169
|
)
|
|
164
170
|
|
|
171
|
+
env = get_sql_cli_jinja_env(
|
|
172
|
+
loader=jinja2.loaders.FileSystemLoader(self.project_root)
|
|
173
|
+
)
|
|
174
|
+
scripts_content_list = self._expand_script_templates(
|
|
175
|
+
env, cli_context.template_context, sql_scripts_paths
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
for index, sql_script_path in enumerate(sql_scripts_paths):
|
|
179
|
+
cc.step(f"Executing SQL script: {sql_script_path}")
|
|
180
|
+
self._execute_sql_script(scripts_content_list[index], self.app_name)
|
|
181
|
+
|
|
165
182
|
def get_all_existing_versions(self) -> SnowflakeCursor:
|
|
166
183
|
"""
|
|
167
184
|
Get all existing versions, if defined, for an application package.
|
|
@@ -223,7 +240,7 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
223
240
|
f"The following objects are owned by application {self.app_name} and need to be dropped:\n{application_objects_str}"
|
|
224
241
|
)
|
|
225
242
|
except ProgrammingError as err:
|
|
226
|
-
if err.errno !=
|
|
243
|
+
if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
227
244
|
generic_sql_error_handler(err)
|
|
228
245
|
cc.warning(
|
|
229
246
|
"The application owns other objects but they could not be determined."
|
|
@@ -242,10 +259,12 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
242
259
|
)
|
|
243
260
|
raise typer.Exit(1)
|
|
244
261
|
try:
|
|
262
|
+
cascade_msg = " (cascade)" if cascade else ""
|
|
263
|
+
cc.step(f"Dropping application object {self.app_name}{cascade_msg}.")
|
|
245
264
|
cascade_sql = " cascade" if cascade else ""
|
|
246
265
|
self._execute_query(f"drop application {self.app_name}{cascade_sql}")
|
|
247
266
|
except ProgrammingError as err:
|
|
248
|
-
if
|
|
267
|
+
if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
|
|
249
268
|
# We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
|
|
250
269
|
return self.drop_application_before_upgrade(
|
|
251
270
|
policy, is_interactive, cascade=True
|
|
@@ -262,89 +281,85 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
262
281
|
with self.use_role(self.app_role):
|
|
263
282
|
|
|
264
283
|
# 1. Need to use a warehouse to create an application object
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
284
|
+
with self.use_warehouse(self.application_warehouse):
|
|
285
|
+
|
|
286
|
+
# 2. Check for an existing application by the same name
|
|
287
|
+
show_app_row = self.get_existing_app_info()
|
|
288
|
+
|
|
289
|
+
# 3. If existing application is found, perform a few validations and upgrade the application object.
|
|
290
|
+
if show_app_row:
|
|
272
291
|
|
|
273
|
-
|
|
274
|
-
show_app_row = self.get_existing_app_info()
|
|
292
|
+
install_method.ensure_app_usable(self._na_project, show_app_row)
|
|
275
293
|
|
|
276
|
-
|
|
277
|
-
|
|
294
|
+
# If all the above checks are in order, proceed to upgrade
|
|
295
|
+
try:
|
|
296
|
+
cc.step(
|
|
297
|
+
f"Upgrading existing application object {self.app_name}."
|
|
298
|
+
)
|
|
299
|
+
using_clause = install_method.using_clause(self._na_project)
|
|
300
|
+
self._execute_query(
|
|
301
|
+
f"alter application {self.app_name} upgrade {using_clause}"
|
|
302
|
+
)
|
|
278
303
|
|
|
279
|
-
|
|
304
|
+
if install_method.is_dev_mode:
|
|
305
|
+
# if debug_mode is present (controlled), ensure it is up-to-date
|
|
306
|
+
if self.debug_mode is not None:
|
|
307
|
+
self._execute_query(
|
|
308
|
+
f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# hooks always executed after a create or upgrade
|
|
312
|
+
self._execute_post_deploy_hooks()
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
except ProgrammingError as err:
|
|
316
|
+
if err.errno not in UPGRADE_RESTRICTION_CODES:
|
|
317
|
+
generic_sql_error_handler(err=err)
|
|
318
|
+
else: # The existing application object was created from a different process.
|
|
319
|
+
cc.warning(err.msg)
|
|
320
|
+
self.drop_application_before_upgrade(policy, is_interactive)
|
|
321
|
+
|
|
322
|
+
# 4. With no (more) existing application objects, create an application object using the release directives
|
|
323
|
+
cc.step(f"Creating new application object {self.app_name} in account.")
|
|
324
|
+
|
|
325
|
+
if self.app_role != self.package_role:
|
|
326
|
+
with self.use_role(self.package_role):
|
|
327
|
+
self._execute_query(
|
|
328
|
+
f"grant install, develop on application package {self.package_name} to role {self.app_role}"
|
|
329
|
+
)
|
|
330
|
+
self._execute_query(
|
|
331
|
+
f"grant usage on schema {self.package_name}.{self.stage_schema} to role {self.app_role}"
|
|
332
|
+
)
|
|
333
|
+
self._execute_query(
|
|
334
|
+
f"grant read on stage {self.stage_fqn} to role {self.app_role}"
|
|
335
|
+
)
|
|
280
336
|
|
|
281
|
-
# If all the above checks are in order, proceed to upgrade
|
|
282
337
|
try:
|
|
283
|
-
|
|
338
|
+
# by default, applications are created in debug mode when possible;
|
|
339
|
+
# this can be overridden in the project definition
|
|
340
|
+
debug_mode_clause = ""
|
|
341
|
+
if install_method.is_dev_mode:
|
|
342
|
+
initial_debug_mode = (
|
|
343
|
+
self.debug_mode if self.debug_mode is not None else True
|
|
344
|
+
)
|
|
345
|
+
debug_mode_clause = f"debug_mode = {initial_debug_mode}"
|
|
346
|
+
|
|
284
347
|
using_clause = install_method.using_clause(self._na_project)
|
|
285
348
|
self._execute_query(
|
|
286
|
-
|
|
349
|
+
dedent(
|
|
350
|
+
f"""\
|
|
351
|
+
create application {self.app_name}
|
|
352
|
+
from application package {self.package_name} {using_clause} {debug_mode_clause}
|
|
353
|
+
comment = {SPECIAL_COMMENT}
|
|
354
|
+
"""
|
|
355
|
+
)
|
|
287
356
|
)
|
|
288
357
|
|
|
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
358
|
# hooks always executed after a create or upgrade
|
|
297
359
|
self._execute_post_deploy_hooks()
|
|
298
|
-
return
|
|
299
360
|
|
|
300
361
|
except ProgrammingError as err:
|
|
301
|
-
|
|
302
|
-
generic_sql_error_handler(err=err)
|
|
303
|
-
else: # The existing application object was created from a different process.
|
|
304
|
-
cc.warning(err.msg)
|
|
305
|
-
self.drop_application_before_upgrade(policy, is_interactive)
|
|
306
|
-
|
|
307
|
-
# 4. With no (more) existing application objects, create an application object using the release directives
|
|
308
|
-
cc.step(f"Creating new application object {self.app_name} in account.")
|
|
309
|
-
|
|
310
|
-
if self.app_role != self.package_role:
|
|
311
|
-
with self.use_role(self.package_role):
|
|
312
|
-
self._execute_query(
|
|
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}"
|
|
320
|
-
)
|
|
321
|
-
|
|
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)
|
|
333
|
-
self._execute_query(
|
|
334
|
-
dedent(
|
|
335
|
-
f"""\
|
|
336
|
-
create application {self.app_name}
|
|
337
|
-
from application package {self.package_name} {using_clause} {debug_mode_clause}
|
|
338
|
-
comment = {SPECIAL_COMMENT}
|
|
339
|
-
"""
|
|
340
|
-
)
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# hooks always executed after a create or upgrade
|
|
344
|
-
self._execute_post_deploy_hooks()
|
|
345
|
-
|
|
346
|
-
except ProgrammingError as err:
|
|
347
|
-
generic_sql_error_handler(err)
|
|
362
|
+
generic_sql_error_handler(err)
|
|
348
363
|
|
|
349
364
|
def process(
|
|
350
365
|
self,
|
|
@@ -20,6 +20,7 @@ from typing import Dict, Optional
|
|
|
20
20
|
|
|
21
21
|
import typer
|
|
22
22
|
from snowflake.cli.api.console import cli_console as cc
|
|
23
|
+
from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
|
|
23
24
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
24
25
|
from snowflake.cli.plugins.nativeapp.constants import (
|
|
25
26
|
ALLOWED_SPECIAL_COMMENTS,
|
|
@@ -28,7 +29,6 @@ from snowflake.cli.plugins.nativeapp.constants import (
|
|
|
28
29
|
INTERNAL_DISTRIBUTION,
|
|
29
30
|
OWNER_COL,
|
|
30
31
|
)
|
|
31
|
-
from snowflake.cli.plugins.nativeapp.errno import APPLICATION_NO_LONGER_AVAILABLE
|
|
32
32
|
from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
33
33
|
CouldNotDropApplicationPackageWithVersions,
|
|
34
34
|
)
|
|
@@ -226,8 +226,13 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
226
226
|
)
|
|
227
227
|
if show_versions_cursor.rowcount is None:
|
|
228
228
|
raise SnowflakeSQLExecutionError(show_versions_query)
|
|
229
|
+
|
|
229
230
|
if show_versions_cursor.rowcount > 0:
|
|
230
|
-
|
|
231
|
+
# allow dropping a package with versions when --force is set
|
|
232
|
+
if not auto_yes:
|
|
233
|
+
raise CouldNotDropApplicationPackageWithVersions(
|
|
234
|
+
"Drop versions first, or use --force to override."
|
|
235
|
+
)
|
|
231
236
|
|
|
232
237
|
# 4. Check distribution of the existing application package
|
|
233
238
|
actual_distribution = self.get_app_pkg_distribution_in_snowflake
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from functools import wraps
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Union
|
|
20
|
+
|
|
21
|
+
from click import ClickException
|
|
22
|
+
from snowflake.cli.api.cli_global_context import cli_context, cli_context_manager
|
|
23
|
+
from snowflake.cli.api.project.schemas.entities.application_entity import (
|
|
24
|
+
ApplicationEntity,
|
|
25
|
+
)
|
|
26
|
+
from snowflake.cli.api.project.schemas.entities.application_package_entity import (
|
|
27
|
+
ApplicationPackageEntity,
|
|
28
|
+
)
|
|
29
|
+
from snowflake.cli.api.project.schemas.native_app.application import (
|
|
30
|
+
ApplicationPostDeployHook,
|
|
31
|
+
SqlScriptHookType,
|
|
32
|
+
)
|
|
33
|
+
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
34
|
+
from snowflake.cli.api.project.schemas.project_definition import (
|
|
35
|
+
DefinitionV11,
|
|
36
|
+
DefinitionV20,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _convert_v2_artifact_to_v1_dict(
|
|
41
|
+
v2_artifact: Union[PathMapping, Path]
|
|
42
|
+
) -> Union[Dict, str]:
|
|
43
|
+
if isinstance(v2_artifact, PathMapping):
|
|
44
|
+
return {
|
|
45
|
+
"src": v2_artifact.src,
|
|
46
|
+
"dest": v2_artifact.dest,
|
|
47
|
+
"processors": v2_artifact.processors,
|
|
48
|
+
}
|
|
49
|
+
return str(v2_artifact)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _convert_v2_post_deploy_hook_to_v1_scripts(
|
|
53
|
+
v2_post_deploy_hook: ApplicationPostDeployHook,
|
|
54
|
+
) -> List[str]:
|
|
55
|
+
if isinstance(v2_post_deploy_hook, SqlScriptHookType):
|
|
56
|
+
return v2_post_deploy_hook.sql_script
|
|
57
|
+
raise ValueError(f"Unsupported post deploy hook type: {v2_post_deploy_hook}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11:
|
|
61
|
+
pdfv1: Dict[str, Any] = {"definition_version": "1.1", "native_app": {}}
|
|
62
|
+
|
|
63
|
+
app_package_definition: ApplicationPackageEntity = None
|
|
64
|
+
app_definition: Optional[ApplicationEntity] = None
|
|
65
|
+
|
|
66
|
+
for key, entity in v2_definition.entities.items():
|
|
67
|
+
if entity.get_type() == ApplicationPackageEntity.get_type():
|
|
68
|
+
if app_package_definition:
|
|
69
|
+
raise ClickException(
|
|
70
|
+
"More than one application package entity exists in the project definition file."
|
|
71
|
+
)
|
|
72
|
+
app_package_definition = entity
|
|
73
|
+
elif entity.get_type() == ApplicationEntity.get_type():
|
|
74
|
+
if app_definition:
|
|
75
|
+
raise ClickException(
|
|
76
|
+
"More than one application entity exists in the project definition file."
|
|
77
|
+
)
|
|
78
|
+
app_definition = entity
|
|
79
|
+
if not app_package_definition:
|
|
80
|
+
raise ClickException(
|
|
81
|
+
"Could not find an application package entity in the project definition file."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# NativeApp
|
|
85
|
+
if app_definition and app_definition.name:
|
|
86
|
+
pdfv1["native_app"]["name"] = app_definition.name
|
|
87
|
+
else:
|
|
88
|
+
pdfv1["native_app"]["name"] = app_package_definition.name.split("_pkg_")[0]
|
|
89
|
+
pdfv1["native_app"]["artifacts"] = [
|
|
90
|
+
_convert_v2_artifact_to_v1_dict(a) for a in app_package_definition.artifacts
|
|
91
|
+
]
|
|
92
|
+
pdfv1["native_app"]["source_stage"] = app_package_definition.stage
|
|
93
|
+
pdfv1["native_app"]["bundle_root"] = str(app_package_definition.bundle_root)
|
|
94
|
+
pdfv1["native_app"]["generated_root"] = str(app_package_definition.generated_root)
|
|
95
|
+
pdfv1["native_app"]["deploy_root"] = str(app_package_definition.deploy_root)
|
|
96
|
+
|
|
97
|
+
# Package
|
|
98
|
+
pdfv1["native_app"]["package"] = {}
|
|
99
|
+
pdfv1["native_app"]["package"]["name"] = app_package_definition.name
|
|
100
|
+
if app_package_definition.distribution:
|
|
101
|
+
pdfv1["native_app"]["package"][
|
|
102
|
+
"distribution"
|
|
103
|
+
] = app_package_definition.distribution
|
|
104
|
+
if app_package_definition.meta and app_package_definition.meta.post_deploy:
|
|
105
|
+
pdfv1["native_app"]["package"]["scripts"] = [
|
|
106
|
+
_convert_v2_post_deploy_hook_to_v1_scripts(s)
|
|
107
|
+
for s in app_package_definition.meta.post_deploy
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
# Application
|
|
111
|
+
if app_definition:
|
|
112
|
+
pdfv1["native_app"]["application"] = {}
|
|
113
|
+
pdfv1["native_app"]["application"]["name"] = app_definition.name
|
|
114
|
+
if app_definition.meta and app_definition.meta.role:
|
|
115
|
+
pdfv1["native_app"]["application"]["role"] = app_definition.meta.role
|
|
116
|
+
if app_definition.meta and app_definition.meta.post_deploy:
|
|
117
|
+
pdfv1["native_app"]["application"][
|
|
118
|
+
"post_deploy"
|
|
119
|
+
] = app_definition.meta.post_deploy
|
|
120
|
+
|
|
121
|
+
# Override the definition object in global context
|
|
122
|
+
return DefinitionV11(**pdfv1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def nativeapp_definition_v2_to_v1(func):
|
|
126
|
+
"""
|
|
127
|
+
A command decorator that attempts to automatically convert a native app project from
|
|
128
|
+
definition v2 to v1.1. Assumes with_project_definition() has already been called.
|
|
129
|
+
The definition object in CliGlobalContext will be replaced with the converted object.
|
|
130
|
+
Exactly one application package entity type is expected, and up to one application
|
|
131
|
+
entity type is expected.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
@wraps(func)
|
|
135
|
+
def wrapper(*args, **kwargs):
|
|
136
|
+
original_pdf: DefinitionV20 = cli_context.project_definition
|
|
137
|
+
if not original_pdf:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"Project definition could not be found. The nativeapp_definition_v2_to_v1 command decorator assumes with_project_definition() was called before it."
|
|
140
|
+
)
|
|
141
|
+
if original_pdf.definition_version == "2":
|
|
142
|
+
pdfv1 = _pdf_v2_to_v1(original_pdf)
|
|
143
|
+
cli_context_manager.set_project_definition(pdfv1)
|
|
144
|
+
return func(*args, **kwargs)
|
|
145
|
+
|
|
146
|
+
return wrapper
|
|
@@ -25,6 +25,7 @@ from snowflake.cli.api.commands.decorators import (
|
|
|
25
25
|
)
|
|
26
26
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
27
27
|
from snowflake.cli.api.output.types import CommandResult, MessageResult, QueryResult
|
|
28
|
+
from snowflake.cli.api.project.project_verification import assert_project_type
|
|
28
29
|
from snowflake.cli.plugins.nativeapp.common_flags import ForceOption, InteractiveOption
|
|
29
30
|
from snowflake.cli.plugins.nativeapp.policy import (
|
|
30
31
|
AllowAlwaysPolicy,
|
|
@@ -32,6 +33,9 @@ from snowflake.cli.plugins.nativeapp.policy import (
|
|
|
32
33
|
DenyAlwaysPolicy,
|
|
33
34
|
)
|
|
34
35
|
from snowflake.cli.plugins.nativeapp.run_processor import NativeAppRunProcessor
|
|
36
|
+
from snowflake.cli.plugins.nativeapp.v2_conversions.v2_to_v1_decorator import (
|
|
37
|
+
nativeapp_definition_v2_to_v1,
|
|
38
|
+
)
|
|
35
39
|
from snowflake.cli.plugins.nativeapp.version.version_processor import (
|
|
36
40
|
NativeAppVersionCreateProcessor,
|
|
37
41
|
NativeAppVersionDropProcessor,
|
|
@@ -46,7 +50,8 @@ log = logging.getLogger(__name__)
|
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
@app.command(requires_connection=True)
|
|
49
|
-
@with_project_definition(
|
|
53
|
+
@with_project_definition()
|
|
54
|
+
@nativeapp_definition_v2_to_v1
|
|
50
55
|
def create(
|
|
51
56
|
version: Optional[str] = typer.Argument(
|
|
52
57
|
None,
|
|
@@ -71,6 +76,9 @@ def create(
|
|
|
71
76
|
"""
|
|
72
77
|
Adds a new patch to the provided version defined in your application package. If the version does not exist, creates a version with patch 0.
|
|
73
78
|
"""
|
|
79
|
+
|
|
80
|
+
assert_project_type("native_app")
|
|
81
|
+
|
|
74
82
|
if version is None and patch is not None:
|
|
75
83
|
raise MissingParameter("Cannot provide a patch without version!")
|
|
76
84
|
|
|
@@ -107,13 +115,17 @@ def create(
|
|
|
107
115
|
|
|
108
116
|
|
|
109
117
|
@app.command("list", requires_connection=True)
|
|
110
|
-
@with_project_definition(
|
|
118
|
+
@with_project_definition()
|
|
119
|
+
@nativeapp_definition_v2_to_v1
|
|
111
120
|
def version_list(
|
|
112
121
|
**options,
|
|
113
122
|
) -> CommandResult:
|
|
114
123
|
"""
|
|
115
124
|
Lists all versions defined in an application package.
|
|
116
125
|
"""
|
|
126
|
+
|
|
127
|
+
assert_project_type("native_app")
|
|
128
|
+
|
|
117
129
|
processor = NativeAppRunProcessor(
|
|
118
130
|
project_definition=cli_context.project_definition.native_app,
|
|
119
131
|
project_root=cli_context.project_root,
|
|
@@ -123,7 +135,8 @@ def version_list(
|
|
|
123
135
|
|
|
124
136
|
|
|
125
137
|
@app.command(requires_connection=True)
|
|
126
|
-
@with_project_definition(
|
|
138
|
+
@with_project_definition()
|
|
139
|
+
@nativeapp_definition_v2_to_v1
|
|
127
140
|
def drop(
|
|
128
141
|
version: Optional[str] = typer.Argument(
|
|
129
142
|
None,
|
|
@@ -137,6 +150,9 @@ def drop(
|
|
|
137
150
|
Drops a version defined in your application package. Versions can either be passed in as an argument to the command or read from the `manifest.yml` file.
|
|
138
151
|
Dropping patches is not allowed.
|
|
139
152
|
"""
|
|
153
|
+
|
|
154
|
+
assert_project_type("native_app")
|
|
155
|
+
|
|
140
156
|
is_interactive = False
|
|
141
157
|
if force:
|
|
142
158
|
policy = AllowAlwaysPolicy()
|
|
@@ -34,6 +34,7 @@ from snowflake.cli.plugins.nativeapp.artifacts import (
|
|
|
34
34
|
)
|
|
35
35
|
from snowflake.cli.plugins.nativeapp.constants import VERSION_COL
|
|
36
36
|
from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
37
|
+
ApplicationPackageAlreadyExistsError,
|
|
37
38
|
ApplicationPackageDoesNotExistError,
|
|
38
39
|
)
|
|
39
40
|
from snowflake.cli.plugins.nativeapp.manager import (
|
|
@@ -64,12 +65,14 @@ def check_index_changes_in_git_repo(
|
|
|
64
65
|
# Check if the repo has any changes, including untracked files
|
|
65
66
|
if repo.is_dirty(untracked_files=True):
|
|
66
67
|
cc.warning(
|
|
67
|
-
"Changes detected in the git repository.
|
|
68
|
+
"Changes detected in the git repository. "
|
|
69
|
+
"(Rerun your command with --skip-git-check flag to ignore this check)"
|
|
68
70
|
)
|
|
69
71
|
repo.git.execute(["git", "status"])
|
|
70
72
|
|
|
71
73
|
user_prompt = (
|
|
72
|
-
"You have local changes in this repository that are not part of a previous commit.
|
|
74
|
+
"You have local changes in this repository that are not part of a previous commit. "
|
|
75
|
+
"Do you still want to continue?"
|
|
73
76
|
)
|
|
74
77
|
if not policy.should_proceed(user_prompt):
|
|
75
78
|
if is_interactive:
|
|
@@ -212,7 +215,12 @@ class NativeAppVersionCreateProcessor(NativeAppRunProcessor):
|
|
|
212
215
|
is_interactive=is_interactive,
|
|
213
216
|
)
|
|
214
217
|
|
|
215
|
-
|
|
218
|
+
try:
|
|
219
|
+
self.create_app_package()
|
|
220
|
+
except ApplicationPackageAlreadyExistsError as e:
|
|
221
|
+
cc.warning(e.message)
|
|
222
|
+
if not policy.should_proceed("Proceed with using this package?"):
|
|
223
|
+
raise typer.Abort() from e
|
|
216
224
|
|
|
217
225
|
with self.use_role(self.package_role):
|
|
218
226
|
# Now that the application package exists, create shared data
|
|
@@ -33,7 +33,7 @@ app = SnowTyperFactory(
|
|
|
33
33
|
|
|
34
34
|
NameArgument = typer.Argument(help="Name of the object")
|
|
35
35
|
ObjectArgument = typer.Argument(
|
|
36
|
-
help="Type of object. For example table,
|
|
36
|
+
help="Type of object. For example table, database, compute-pool.",
|
|
37
37
|
case_sensitive=False,
|
|
38
38
|
show_default=False,
|
|
39
39
|
)
|
|
@@ -36,16 +36,6 @@ def _get_object_names(object_type: str) -> ObjectNames:
|
|
|
36
36
|
return OBJECT_TO_NAMES[object_type]
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def _pluralize_object_type(object_type: str) -> str:
|
|
40
|
-
"""
|
|
41
|
-
Pluralize object type without depending on OBJECT_TO_NAMES.
|
|
42
|
-
"""
|
|
43
|
-
if object_type.endswith("y"):
|
|
44
|
-
return object_type[:-1].lower() + "ies"
|
|
45
|
-
else:
|
|
46
|
-
return object_type.lower() + "s"
|
|
47
|
-
|
|
48
|
-
|
|
49
39
|
class ObjectManager(SqlExecutionMixin):
|
|
50
40
|
def show(
|
|
51
41
|
self,
|
|
@@ -85,11 +75,8 @@ class ObjectManager(SqlExecutionMixin):
|
|
|
85
75
|
|
|
86
76
|
def create(self, object_type: str, object_data: Dict[str, Any]) -> str:
|
|
87
77
|
rest = RestApi(self._conn)
|
|
88
|
-
url = rest.determine_url_for_create_query(
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
if not url:
|
|
92
|
-
return f"Create operation for type {object_type} is not supported. Try using `sql -q 'CREATE ...'` command"
|
|
78
|
+
url = rest.determine_url_for_create_query(object_type=object_type)
|
|
79
|
+
|
|
93
80
|
try:
|
|
94
81
|
response = rest.send_rest_request(url=url, method="post", data=object_data)
|
|
95
82
|
# workaround as SnowflakeRestful class ignores some errors, dropping their info and returns {} instead.
|