snowflake-cli-labs 3.0.0rc1__py3-none-any.whl → 3.0.0rc3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +10 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
- snowflake/cli/_app/secret.py +9 -0
- snowflake/cli/_app/snow_connector.py +110 -51
- snowflake/cli/_app/telemetry.py +8 -4
- snowflake/cli/_app/version_check.py +74 -0
- snowflake/cli/_plugins/git/commands.py +55 -14
- snowflake/cli/_plugins/git/manager.py +53 -7
- snowflake/cli/_plugins/helpers/commands.py +57 -0
- snowflake/cli/{api/commands/typer_pre_execute.py → _plugins/helpers/plugin_spec.py} +14 -10
- snowflake/cli/_plugins/nativeapp/application_entity.py +651 -0
- snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_entity_model.py +2 -2
- snowflake/cli/_plugins/nativeapp/application_package_entity.py +1107 -0
- snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_package_entity_model.py +3 -3
- snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +3 -6
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +50 -32
- snowflake/cli/_plugins/nativeapp/commands.py +84 -16
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
- snowflake/cli/_plugins/nativeapp/manager.py +56 -92
- snowflake/cli/_plugins/nativeapp/policy.py +3 -0
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
- snowflake/cli/_plugins/nativeapp/run_processor.py +65 -272
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +70 -0
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +11 -154
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +150 -40
- snowflake/cli/_plugins/nativeapp/version/commands.py +6 -24
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +35 -235
- snowflake/cli/_plugins/snowpark/commands.py +5 -5
- snowflake/cli/_plugins/snowpark/common.py +4 -4
- snowflake/cli/_plugins/snowpark/models.py +2 -1
- snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
- snowflake/cli/_plugins/stage/manager.py +9 -4
- snowflake/cli/_plugins/streamlit/commands.py +4 -4
- snowflake/cli/_plugins/streamlit/manager.py +17 -4
- snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
- snowflake/cli/_plugins/workspace/action_context.py +2 -1
- snowflake/cli/_plugins/workspace/commands.py +127 -48
- snowflake/cli/_plugins/workspace/manager.py +1 -0
- snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
- snowflake/cli/api/cli_global_context.py +136 -313
- snowflake/cli/api/commands/flags.py +76 -91
- snowflake/cli/api/commands/snow_typer.py +7 -5
- snowflake/cli/api/config.py +1 -1
- snowflake/cli/api/connections.py +214 -0
- snowflake/cli/api/console/abc.py +4 -2
- snowflake/cli/api/entities/common.py +4 -0
- snowflake/cli/api/entities/utils.py +41 -31
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/identifiers.py +7 -3
- snowflake/cli/api/project/definition.py +11 -0
- snowflake/cli/api/project/definition_conversion.py +175 -16
- snowflake/cli/api/project/schemas/entities/common.py +15 -14
- snowflake/cli/api/project/schemas/entities/entities.py +13 -10
- snowflake/cli/api/project/schemas/project_definition.py +107 -45
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
- snowflake/cli/api/rendering/project_definition_templates.py +4 -0
- snowflake/cli/api/rendering/sql_templates.py +7 -0
- snowflake/cli/api/sql_execution.py +6 -15
- snowflake/cli/api/utils/definition_rendering.py +3 -1
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/METADATA +9 -9
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/RECORD +88 -81
- snowflake/cli/api/entities/application_entity.py +0 -12
- snowflake/cli/api/entities/application_package_entity.py +0 -553
- snowflake/cli/api/project/schemas/snowpark/__init__.py +0 -13
- snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
- /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
- /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from textwrap import dedent
|
|
5
|
+
from typing import Callable, List, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from click import BadOptionUsage, ClickException
|
|
9
|
+
from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
|
|
10
|
+
ApplicationPackageEntityModel,
|
|
11
|
+
)
|
|
12
|
+
from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
13
|
+
BundleMap,
|
|
14
|
+
build_bundle,
|
|
15
|
+
find_version_info_in_manifest_file,
|
|
16
|
+
)
|
|
17
|
+
from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
|
|
18
|
+
from snowflake.cli._plugins.nativeapp.codegen.compiler import NativeAppCompiler
|
|
19
|
+
from snowflake.cli._plugins.nativeapp.constants import (
|
|
20
|
+
ALLOWED_SPECIAL_COMMENTS,
|
|
21
|
+
COMMENT_COL,
|
|
22
|
+
EXTERNAL_DISTRIBUTION,
|
|
23
|
+
INTERNAL_DISTRIBUTION,
|
|
24
|
+
NAME_COL,
|
|
25
|
+
OWNER_COL,
|
|
26
|
+
PATCH_COL,
|
|
27
|
+
SPECIAL_COMMENT,
|
|
28
|
+
VERSION_COL,
|
|
29
|
+
)
|
|
30
|
+
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
31
|
+
ApplicationPackageAlreadyExistsError,
|
|
32
|
+
ApplicationPackageDoesNotExistError,
|
|
33
|
+
CouldNotDropApplicationPackageWithVersions,
|
|
34
|
+
SetupScriptFailedValidation,
|
|
35
|
+
)
|
|
36
|
+
from snowflake.cli._plugins.nativeapp.policy import (
|
|
37
|
+
AllowAlwaysPolicy,
|
|
38
|
+
AskAlwaysPolicy,
|
|
39
|
+
DenyAlwaysPolicy,
|
|
40
|
+
PolicyBase,
|
|
41
|
+
)
|
|
42
|
+
from snowflake.cli._plugins.nativeapp.utils import (
|
|
43
|
+
needs_confirmation,
|
|
44
|
+
)
|
|
45
|
+
from snowflake.cli._plugins.stage.diff import DiffResult
|
|
46
|
+
from snowflake.cli._plugins.stage.manager import StageManager
|
|
47
|
+
from snowflake.cli._plugins.workspace.action_context import ActionContext
|
|
48
|
+
from snowflake.cli.api.console import cli_console as cc
|
|
49
|
+
from snowflake.cli.api.console.abc import AbstractConsole
|
|
50
|
+
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
51
|
+
from snowflake.cli.api.entities.utils import (
|
|
52
|
+
drop_generic_object,
|
|
53
|
+
execute_post_deploy_hooks,
|
|
54
|
+
generic_sql_error_handler,
|
|
55
|
+
render_script_templates,
|
|
56
|
+
sync_deploy_root_with_stage,
|
|
57
|
+
validation_item_to_str,
|
|
58
|
+
)
|
|
59
|
+
from snowflake.cli.api.errno import (
|
|
60
|
+
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
61
|
+
)
|
|
62
|
+
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
63
|
+
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
64
|
+
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
65
|
+
from snowflake.cli.api.project.util import (
|
|
66
|
+
extract_schema,
|
|
67
|
+
identifier_to_show_like_pattern,
|
|
68
|
+
to_identifier,
|
|
69
|
+
unquote_identifier,
|
|
70
|
+
)
|
|
71
|
+
from snowflake.cli.api.rendering.jinja import (
|
|
72
|
+
get_basic_jinja_env,
|
|
73
|
+
)
|
|
74
|
+
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
75
|
+
from snowflake.connector import ProgrammingError
|
|
76
|
+
from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
80
|
+
"""
|
|
81
|
+
A Native App application package.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def action_bundle(self, ctx: ActionContext, *args, **kwargs):
|
|
85
|
+
model = self._entity_model
|
|
86
|
+
return self.bundle(
|
|
87
|
+
project_root=ctx.project_root,
|
|
88
|
+
deploy_root=Path(model.deploy_root),
|
|
89
|
+
bundle_root=Path(model.bundle_root),
|
|
90
|
+
generated_root=Path(model.generated_root),
|
|
91
|
+
package_name=model.identifier,
|
|
92
|
+
artifacts=model.artifacts,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def action_deploy(
|
|
96
|
+
self,
|
|
97
|
+
ctx: ActionContext,
|
|
98
|
+
prune: bool,
|
|
99
|
+
recursive: bool,
|
|
100
|
+
paths: List[Path],
|
|
101
|
+
validate: bool,
|
|
102
|
+
interactive: bool,
|
|
103
|
+
force: bool,
|
|
104
|
+
stage_fqn: Optional[str] = None,
|
|
105
|
+
*args,
|
|
106
|
+
**kwargs,
|
|
107
|
+
):
|
|
108
|
+
model = self._entity_model
|
|
109
|
+
package_name = model.fqn.identifier
|
|
110
|
+
|
|
111
|
+
if force:
|
|
112
|
+
policy = AllowAlwaysPolicy()
|
|
113
|
+
elif interactive:
|
|
114
|
+
policy = AskAlwaysPolicy()
|
|
115
|
+
else:
|
|
116
|
+
policy = DenyAlwaysPolicy()
|
|
117
|
+
|
|
118
|
+
return self.deploy(
|
|
119
|
+
console=ctx.console,
|
|
120
|
+
project_root=ctx.project_root,
|
|
121
|
+
deploy_root=Path(model.deploy_root),
|
|
122
|
+
bundle_root=Path(model.bundle_root),
|
|
123
|
+
generated_root=Path(model.generated_root),
|
|
124
|
+
artifacts=model.artifacts,
|
|
125
|
+
bundle_map=None,
|
|
126
|
+
package_name=package_name,
|
|
127
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
128
|
+
package_distribution=model.distribution,
|
|
129
|
+
prune=prune,
|
|
130
|
+
recursive=recursive,
|
|
131
|
+
paths=paths,
|
|
132
|
+
print_diff=True,
|
|
133
|
+
validate=validate,
|
|
134
|
+
stage_fqn=stage_fqn or f"{package_name}.{model.stage}",
|
|
135
|
+
package_warehouse=(
|
|
136
|
+
(model.meta and model.meta.warehouse) or ctx.default_warehouse
|
|
137
|
+
),
|
|
138
|
+
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
139
|
+
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
140
|
+
policy=policy,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def action_drop(self, ctx: ActionContext, force_drop: bool, *args, **kwargs):
|
|
144
|
+
model = self._entity_model
|
|
145
|
+
package_name = model.fqn.identifier
|
|
146
|
+
if model.meta and model.meta.role:
|
|
147
|
+
package_role = model.meta.role
|
|
148
|
+
else:
|
|
149
|
+
package_role = ctx.default_role
|
|
150
|
+
|
|
151
|
+
self.drop(
|
|
152
|
+
console=ctx.console,
|
|
153
|
+
package_name=package_name,
|
|
154
|
+
package_role=package_role,
|
|
155
|
+
force_drop=force_drop,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def action_validate(
|
|
159
|
+
self, ctx: ActionContext, interactive: bool, force: bool, *args, **kwargs
|
|
160
|
+
):
|
|
161
|
+
model = self._entity_model
|
|
162
|
+
package_name = model.fqn.identifier
|
|
163
|
+
stage_fqn = f"{package_name}.{model.stage}"
|
|
164
|
+
if model.meta and model.meta.role:
|
|
165
|
+
package_role = model.meta.role
|
|
166
|
+
else:
|
|
167
|
+
package_role = ctx.default_role
|
|
168
|
+
|
|
169
|
+
def deploy_to_scratch_stage_fn():
|
|
170
|
+
self.action_deploy(
|
|
171
|
+
ctx=ctx,
|
|
172
|
+
prune=True,
|
|
173
|
+
recursive=True,
|
|
174
|
+
paths=[],
|
|
175
|
+
validate=False,
|
|
176
|
+
stage_fqn=f"{package_name}.{model.scratch_stage}",
|
|
177
|
+
interactive=interactive,
|
|
178
|
+
force=force,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
self.validate_setup_script(
|
|
182
|
+
console=ctx.console,
|
|
183
|
+
package_name=package_name,
|
|
184
|
+
package_role=package_role,
|
|
185
|
+
stage_fqn=stage_fqn,
|
|
186
|
+
use_scratch_stage=True,
|
|
187
|
+
scratch_stage_fqn=f"{package_name}.{model.scratch_stage}",
|
|
188
|
+
deploy_to_scratch_stage_fn=deploy_to_scratch_stage_fn,
|
|
189
|
+
)
|
|
190
|
+
ctx.console.message("Setup script is valid")
|
|
191
|
+
|
|
192
|
+
def action_version_list(
|
|
193
|
+
self, ctx: ActionContext, *args, **kwargs
|
|
194
|
+
) -> SnowflakeCursor:
|
|
195
|
+
model = self._entity_model
|
|
196
|
+
return self.version_list(
|
|
197
|
+
package_name=model.fqn.identifier,
|
|
198
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def action_version_create(
|
|
202
|
+
self,
|
|
203
|
+
ctx: ActionContext,
|
|
204
|
+
version: Optional[str],
|
|
205
|
+
patch: Optional[int],
|
|
206
|
+
skip_git_check: bool,
|
|
207
|
+
interactive: bool,
|
|
208
|
+
force: bool,
|
|
209
|
+
*args,
|
|
210
|
+
**kwargs,
|
|
211
|
+
):
|
|
212
|
+
model = self._entity_model
|
|
213
|
+
package_name = model.fqn.identifier
|
|
214
|
+
return self.version_create(
|
|
215
|
+
console=ctx.console,
|
|
216
|
+
project_root=ctx.project_root,
|
|
217
|
+
deploy_root=Path(model.deploy_root),
|
|
218
|
+
bundle_root=Path(model.bundle_root),
|
|
219
|
+
generated_root=Path(model.generated_root),
|
|
220
|
+
artifacts=model.artifacts,
|
|
221
|
+
package_name=package_name,
|
|
222
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
223
|
+
package_distribution=model.distribution,
|
|
224
|
+
prune=True,
|
|
225
|
+
recursive=True,
|
|
226
|
+
paths=None,
|
|
227
|
+
print_diff=True,
|
|
228
|
+
validate=True,
|
|
229
|
+
stage_fqn=f"{package_name}.{model.stage}",
|
|
230
|
+
package_warehouse=(
|
|
231
|
+
(model.meta and model.meta.warehouse) or ctx.default_warehouse
|
|
232
|
+
),
|
|
233
|
+
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
234
|
+
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
235
|
+
version=version,
|
|
236
|
+
patch=patch,
|
|
237
|
+
skip_git_check=skip_git_check,
|
|
238
|
+
force=force,
|
|
239
|
+
interactive=interactive,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def bundle(
|
|
244
|
+
project_root: Path,
|
|
245
|
+
deploy_root: Path,
|
|
246
|
+
bundle_root: Path,
|
|
247
|
+
generated_root: Path,
|
|
248
|
+
artifacts: list[PathMapping],
|
|
249
|
+
package_name: str,
|
|
250
|
+
):
|
|
251
|
+
bundle_map = build_bundle(project_root, deploy_root, artifacts)
|
|
252
|
+
bundle_context = BundleContext(
|
|
253
|
+
package_name=package_name,
|
|
254
|
+
artifacts=artifacts,
|
|
255
|
+
project_root=project_root,
|
|
256
|
+
bundle_root=bundle_root,
|
|
257
|
+
deploy_root=deploy_root,
|
|
258
|
+
generated_root=generated_root,
|
|
259
|
+
)
|
|
260
|
+
compiler = NativeAppCompiler(bundle_context)
|
|
261
|
+
compiler.compile_artifacts()
|
|
262
|
+
return bundle_map
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def deploy(
|
|
266
|
+
cls,
|
|
267
|
+
console: AbstractConsole,
|
|
268
|
+
project_root: Path,
|
|
269
|
+
deploy_root: Path,
|
|
270
|
+
bundle_root: Path,
|
|
271
|
+
generated_root: Path,
|
|
272
|
+
artifacts: list[PathMapping],
|
|
273
|
+
bundle_map: BundleMap | None,
|
|
274
|
+
package_name: str,
|
|
275
|
+
package_role: str,
|
|
276
|
+
package_distribution: str,
|
|
277
|
+
package_warehouse: str | None,
|
|
278
|
+
prune: bool,
|
|
279
|
+
recursive: bool,
|
|
280
|
+
paths: List[Path] | None,
|
|
281
|
+
print_diff: bool,
|
|
282
|
+
validate: bool,
|
|
283
|
+
stage_fqn: str,
|
|
284
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
285
|
+
package_scripts: List[str],
|
|
286
|
+
policy: PolicyBase,
|
|
287
|
+
) -> DiffResult:
|
|
288
|
+
# 1. Create a bundle if one wasn't passed in
|
|
289
|
+
bundle_map = bundle_map or cls.bundle(
|
|
290
|
+
project_root=project_root,
|
|
291
|
+
deploy_root=deploy_root,
|
|
292
|
+
bundle_root=bundle_root,
|
|
293
|
+
generated_root=generated_root,
|
|
294
|
+
artifacts=artifacts,
|
|
295
|
+
package_name=package_name,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# 2. Create an empty application package, if none exists
|
|
299
|
+
try:
|
|
300
|
+
cls.create_app_package(
|
|
301
|
+
console=console,
|
|
302
|
+
package_name=package_name,
|
|
303
|
+
package_role=package_role,
|
|
304
|
+
package_distribution=package_distribution,
|
|
305
|
+
)
|
|
306
|
+
except ApplicationPackageAlreadyExistsError as e:
|
|
307
|
+
cc.warning(e.message)
|
|
308
|
+
if not policy.should_proceed("Proceed with using this package?"):
|
|
309
|
+
raise typer.Abort() from e
|
|
310
|
+
with get_sql_executor().use_role(package_role):
|
|
311
|
+
if package_scripts:
|
|
312
|
+
cls.apply_package_scripts(
|
|
313
|
+
console=console,
|
|
314
|
+
package_scripts=package_scripts,
|
|
315
|
+
package_warehouse=package_warehouse,
|
|
316
|
+
project_root=project_root,
|
|
317
|
+
package_role=package_role,
|
|
318
|
+
package_name=package_name,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# 3. Upload files from deploy root local folder to the above stage
|
|
322
|
+
stage_schema = extract_schema(stage_fqn)
|
|
323
|
+
diff = sync_deploy_root_with_stage(
|
|
324
|
+
console=console,
|
|
325
|
+
deploy_root=deploy_root,
|
|
326
|
+
package_name=package_name,
|
|
327
|
+
stage_schema=stage_schema,
|
|
328
|
+
bundle_map=bundle_map,
|
|
329
|
+
role=package_role,
|
|
330
|
+
prune=prune,
|
|
331
|
+
recursive=recursive,
|
|
332
|
+
stage_fqn=stage_fqn,
|
|
333
|
+
local_paths_to_sync=paths,
|
|
334
|
+
print_diff=print_diff,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if post_deploy_hooks:
|
|
338
|
+
cls.execute_post_deploy_hooks(
|
|
339
|
+
console=console,
|
|
340
|
+
project_root=project_root,
|
|
341
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
342
|
+
package_name=package_name,
|
|
343
|
+
package_warehouse=package_warehouse,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if validate:
|
|
347
|
+
cls.validate_setup_script(
|
|
348
|
+
console=console,
|
|
349
|
+
package_name=package_name,
|
|
350
|
+
package_role=package_role,
|
|
351
|
+
stage_fqn=stage_fqn,
|
|
352
|
+
use_scratch_stage=False,
|
|
353
|
+
scratch_stage_fqn="",
|
|
354
|
+
deploy_to_scratch_stage_fn=lambda *args: None,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return diff
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def version_list(package_name: str, package_role: str) -> SnowflakeCursor:
|
|
361
|
+
"""
|
|
362
|
+
Get all existing versions, if defined, for an application package.
|
|
363
|
+
It executes a 'show versions in application package' query and returns all the results.
|
|
364
|
+
"""
|
|
365
|
+
sql_executor = get_sql_executor()
|
|
366
|
+
with sql_executor.use_role(package_role):
|
|
367
|
+
show_obj_query = f"show versions in application package {package_name}"
|
|
368
|
+
show_obj_cursor = sql_executor.execute_query(show_obj_query)
|
|
369
|
+
|
|
370
|
+
if show_obj_cursor.rowcount is None:
|
|
371
|
+
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
372
|
+
|
|
373
|
+
return show_obj_cursor
|
|
374
|
+
|
|
375
|
+
@classmethod
|
|
376
|
+
def version_create(
|
|
377
|
+
cls,
|
|
378
|
+
console: AbstractConsole,
|
|
379
|
+
project_root: Path,
|
|
380
|
+
deploy_root: Path,
|
|
381
|
+
bundle_root: Path,
|
|
382
|
+
generated_root: Path,
|
|
383
|
+
artifacts: list[PathMapping],
|
|
384
|
+
package_name: str,
|
|
385
|
+
package_role: str,
|
|
386
|
+
package_distribution: str,
|
|
387
|
+
package_warehouse: str | None,
|
|
388
|
+
prune: bool,
|
|
389
|
+
recursive: bool,
|
|
390
|
+
paths: List[Path] | None,
|
|
391
|
+
print_diff: bool,
|
|
392
|
+
validate: bool,
|
|
393
|
+
stage_fqn: str,
|
|
394
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
395
|
+
package_scripts: List[str],
|
|
396
|
+
version: Optional[str],
|
|
397
|
+
patch: Optional[int],
|
|
398
|
+
force: bool,
|
|
399
|
+
interactive: bool,
|
|
400
|
+
skip_git_check: bool,
|
|
401
|
+
):
|
|
402
|
+
"""
|
|
403
|
+
Perform bundle, application package creation, stage upload, version and/or patch to an application package.
|
|
404
|
+
"""
|
|
405
|
+
is_interactive = False
|
|
406
|
+
if force:
|
|
407
|
+
policy = AllowAlwaysPolicy()
|
|
408
|
+
elif interactive:
|
|
409
|
+
is_interactive = True
|
|
410
|
+
policy = AskAlwaysPolicy()
|
|
411
|
+
else:
|
|
412
|
+
policy = DenyAlwaysPolicy()
|
|
413
|
+
|
|
414
|
+
if skip_git_check:
|
|
415
|
+
git_policy = DenyAlwaysPolicy()
|
|
416
|
+
else:
|
|
417
|
+
git_policy = AllowAlwaysPolicy()
|
|
418
|
+
|
|
419
|
+
# Make sure version is not None before proceeding any further.
|
|
420
|
+
# This will raise an exception if version information is not found. Patch can be None.
|
|
421
|
+
bundle_map = None
|
|
422
|
+
if not version:
|
|
423
|
+
console.message(
|
|
424
|
+
dedent(
|
|
425
|
+
f"""\
|
|
426
|
+
Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
|
|
427
|
+
This step will bundle your app artifacts to determine the location of the manifest.yml file.
|
|
428
|
+
"""
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
bundle_map = cls.bundle(
|
|
432
|
+
project_root=project_root,
|
|
433
|
+
deploy_root=deploy_root,
|
|
434
|
+
bundle_root=bundle_root,
|
|
435
|
+
generated_root=generated_root,
|
|
436
|
+
artifacts=artifacts,
|
|
437
|
+
package_name=package_name,
|
|
438
|
+
)
|
|
439
|
+
version, patch = find_version_info_in_manifest_file(deploy_root)
|
|
440
|
+
if not version:
|
|
441
|
+
raise ClickException(
|
|
442
|
+
"Manifest.yml file does not contain a value for the version field."
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Check if --patch needs to throw a bad option error, either if application package does not exist or if version does not exist
|
|
446
|
+
if patch is not None:
|
|
447
|
+
try:
|
|
448
|
+
if not cls.get_existing_version_info(
|
|
449
|
+
version, package_name, package_role
|
|
450
|
+
):
|
|
451
|
+
raise BadOptionUsage(
|
|
452
|
+
option_name="patch",
|
|
453
|
+
message=f"Cannot create a custom patch when version {version} is not defined in the application package {package_name}. Try again without using --patch.",
|
|
454
|
+
)
|
|
455
|
+
except ApplicationPackageDoesNotExistError as app_err:
|
|
456
|
+
raise BadOptionUsage(
|
|
457
|
+
option_name="patch",
|
|
458
|
+
message=f"Cannot create a custom patch when application package {package_name} does not exist. Try again without using --patch.",
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
if git_policy.should_proceed():
|
|
462
|
+
cls.check_index_changes_in_git_repo(
|
|
463
|
+
console=console,
|
|
464
|
+
project_root=project_root,
|
|
465
|
+
policy=policy,
|
|
466
|
+
is_interactive=is_interactive,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
cls.deploy(
|
|
470
|
+
console=console,
|
|
471
|
+
project_root=project_root,
|
|
472
|
+
deploy_root=deploy_root,
|
|
473
|
+
bundle_root=bundle_root,
|
|
474
|
+
generated_root=generated_root,
|
|
475
|
+
artifacts=artifacts,
|
|
476
|
+
bundle_map=bundle_map,
|
|
477
|
+
package_name=package_name,
|
|
478
|
+
package_role=package_role,
|
|
479
|
+
package_distribution=package_distribution,
|
|
480
|
+
prune=prune,
|
|
481
|
+
recursive=recursive,
|
|
482
|
+
paths=paths,
|
|
483
|
+
print_diff=print_diff,
|
|
484
|
+
validate=validate,
|
|
485
|
+
stage_fqn=stage_fqn,
|
|
486
|
+
package_warehouse=package_warehouse,
|
|
487
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
488
|
+
package_scripts=package_scripts,
|
|
489
|
+
policy=policy,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Warn if the version exists in a release directive(s)
|
|
493
|
+
existing_release_directives = (
|
|
494
|
+
cls.get_existing_release_directive_info_for_version(
|
|
495
|
+
package_name, package_role, version
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
if existing_release_directives:
|
|
500
|
+
release_directive_names = ", ".join(
|
|
501
|
+
row["name"] for row in existing_release_directives
|
|
502
|
+
)
|
|
503
|
+
console.warning(
|
|
504
|
+
dedent(
|
|
505
|
+
f"""\
|
|
506
|
+
Version {version} already defined in application package {package_name} and in release directive(s): {release_directive_names}.
|
|
507
|
+
"""
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
user_prompt = (
|
|
512
|
+
f"Are you sure you want to create a new patch for version {version} in application "
|
|
513
|
+
f"package {package_name}? Once added, this operation cannot be undone."
|
|
514
|
+
)
|
|
515
|
+
if not policy.should_proceed(user_prompt):
|
|
516
|
+
if is_interactive:
|
|
517
|
+
console.message("Not creating a new patch.")
|
|
518
|
+
raise typer.Exit(0)
|
|
519
|
+
else:
|
|
520
|
+
console.message(
|
|
521
|
+
"Cannot create a new patch non-interactively without --force."
|
|
522
|
+
)
|
|
523
|
+
raise typer.Exit(1)
|
|
524
|
+
|
|
525
|
+
# Define a new version in the application package
|
|
526
|
+
if not cls.get_existing_version_info(version, package_name, package_role):
|
|
527
|
+
cls.add_new_version(
|
|
528
|
+
console=console,
|
|
529
|
+
package_name=package_name,
|
|
530
|
+
package_role=package_role,
|
|
531
|
+
stage_fqn=stage_fqn,
|
|
532
|
+
version=version,
|
|
533
|
+
)
|
|
534
|
+
return # A new version created automatically has patch 0, we do not need to further increment the patch.
|
|
535
|
+
|
|
536
|
+
# Add a new patch to an existing (old) version
|
|
537
|
+
cls.add_new_patch_to_version(
|
|
538
|
+
console=console,
|
|
539
|
+
package_name=package_name,
|
|
540
|
+
package_role=package_role,
|
|
541
|
+
stage_fqn=stage_fqn,
|
|
542
|
+
version=version,
|
|
543
|
+
patch=patch,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def get_existing_version_info(
|
|
548
|
+
version: str,
|
|
549
|
+
package_name: str,
|
|
550
|
+
package_role: str,
|
|
551
|
+
) -> Optional[dict]:
|
|
552
|
+
"""
|
|
553
|
+
Get the latest patch on an existing version by name in the application package.
|
|
554
|
+
Executes 'show versions like ... in application package' query and returns
|
|
555
|
+
the latest patch in the version as a single row, if one exists. Otherwise,
|
|
556
|
+
returns None.
|
|
557
|
+
"""
|
|
558
|
+
sql_executor = get_sql_executor()
|
|
559
|
+
with sql_executor.use_role(package_role):
|
|
560
|
+
try:
|
|
561
|
+
query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
|
|
562
|
+
cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
563
|
+
|
|
564
|
+
if cursor.rowcount is None:
|
|
565
|
+
raise SnowflakeSQLExecutionError(query)
|
|
566
|
+
|
|
567
|
+
matching_rows = find_all_rows(
|
|
568
|
+
cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
if not matching_rows:
|
|
572
|
+
return None
|
|
573
|
+
|
|
574
|
+
return max(matching_rows, key=lambda row: row[PATCH_COL])
|
|
575
|
+
|
|
576
|
+
except ProgrammingError as err:
|
|
577
|
+
if err.msg.__contains__("does not exist or not authorized"):
|
|
578
|
+
raise ApplicationPackageDoesNotExistError(package_name)
|
|
579
|
+
else:
|
|
580
|
+
generic_sql_error_handler(err=err, role=package_role)
|
|
581
|
+
return None
|
|
582
|
+
|
|
583
|
+
@classmethod
|
|
584
|
+
def get_existing_release_directive_info_for_version(
|
|
585
|
+
cls,
|
|
586
|
+
package_name: str,
|
|
587
|
+
package_role: str,
|
|
588
|
+
version: str,
|
|
589
|
+
) -> List[dict]:
|
|
590
|
+
"""
|
|
591
|
+
Get all existing release directives, if present, set on the version defined in an application package.
|
|
592
|
+
It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
|
|
593
|
+
"""
|
|
594
|
+
sql_executor = get_sql_executor()
|
|
595
|
+
with sql_executor.use_role(package_role):
|
|
596
|
+
show_obj_query = (
|
|
597
|
+
f"show release directives in application package {package_name}"
|
|
598
|
+
)
|
|
599
|
+
show_obj_cursor = sql_executor.execute_query(
|
|
600
|
+
show_obj_query, cursor_class=DictCursor
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
if show_obj_cursor.rowcount is None:
|
|
604
|
+
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
605
|
+
|
|
606
|
+
show_obj_rows = find_all_rows(
|
|
607
|
+
show_obj_cursor,
|
|
608
|
+
lambda row: row[VERSION_COL] == unquote_identifier(version),
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
return show_obj_rows
|
|
612
|
+
|
|
613
|
+
@classmethod
|
|
614
|
+
def add_new_version(
|
|
615
|
+
cls,
|
|
616
|
+
console: AbstractConsole,
|
|
617
|
+
package_name: str,
|
|
618
|
+
package_role: str,
|
|
619
|
+
stage_fqn: str,
|
|
620
|
+
version: str,
|
|
621
|
+
) -> None:
|
|
622
|
+
"""
|
|
623
|
+
Defines a new version in an existing application package.
|
|
624
|
+
"""
|
|
625
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
626
|
+
version = to_identifier(version)
|
|
627
|
+
sql_executor = get_sql_executor()
|
|
628
|
+
with sql_executor.use_role(package_role):
|
|
629
|
+
console.step(
|
|
630
|
+
f"Defining a new version {version} in application package {package_name}"
|
|
631
|
+
)
|
|
632
|
+
add_version_query = dedent(
|
|
633
|
+
f"""\
|
|
634
|
+
alter application package {package_name}
|
|
635
|
+
add version {version}
|
|
636
|
+
using @{stage_fqn}
|
|
637
|
+
"""
|
|
638
|
+
)
|
|
639
|
+
sql_executor.execute_query(add_version_query, cursor_class=DictCursor)
|
|
640
|
+
console.message(
|
|
641
|
+
f"Version {version} created for application package {package_name}."
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
@classmethod
|
|
645
|
+
def add_new_patch_to_version(
|
|
646
|
+
cls,
|
|
647
|
+
console: AbstractConsole,
|
|
648
|
+
package_name: str,
|
|
649
|
+
package_role: str,
|
|
650
|
+
stage_fqn: str,
|
|
651
|
+
version: str,
|
|
652
|
+
patch: Optional[int] = None,
|
|
653
|
+
):
|
|
654
|
+
"""
|
|
655
|
+
Add a new patch, optionally a custom one, to an existing version in an application package.
|
|
656
|
+
"""
|
|
657
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
658
|
+
version = to_identifier(version)
|
|
659
|
+
sql_executor = get_sql_executor()
|
|
660
|
+
with sql_executor.use_role(package_role):
|
|
661
|
+
console.step(
|
|
662
|
+
f"Adding new patch to version {version} defined in application package {package_name}"
|
|
663
|
+
)
|
|
664
|
+
add_version_query = dedent(
|
|
665
|
+
f"""\
|
|
666
|
+
alter application package {package_name}
|
|
667
|
+
add patch {patch if patch else ""} for version {version}
|
|
668
|
+
using @{stage_fqn}
|
|
669
|
+
"""
|
|
670
|
+
)
|
|
671
|
+
result_cursor = sql_executor.execute_query(
|
|
672
|
+
add_version_query, cursor_class=DictCursor
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
show_row = result_cursor.fetchall()[0]
|
|
676
|
+
new_patch = show_row["patch"]
|
|
677
|
+
console.message(
|
|
678
|
+
f"Patch {new_patch} created for version {version} defined in application package {package_name}."
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
@classmethod
|
|
682
|
+
def check_index_changes_in_git_repo(
|
|
683
|
+
cls,
|
|
684
|
+
console: AbstractConsole,
|
|
685
|
+
project_root: Path,
|
|
686
|
+
policy: PolicyBase,
|
|
687
|
+
is_interactive: bool,
|
|
688
|
+
) -> None:
|
|
689
|
+
"""
|
|
690
|
+
Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
|
|
691
|
+
it also checks if there any local changes to the directory that may not be on the application package stage.
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
from git import Repo
|
|
695
|
+
from git.exc import InvalidGitRepositoryError
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
repo = Repo(project_root, search_parent_directories=True)
|
|
699
|
+
assert repo.git_dir is not None
|
|
700
|
+
|
|
701
|
+
# Check if the repo has any changes, including untracked files
|
|
702
|
+
if repo.is_dirty(untracked_files=True):
|
|
703
|
+
console.warning(
|
|
704
|
+
"Changes detected in the git repository. "
|
|
705
|
+
"(Rerun your command with --skip-git-check flag to ignore this check)"
|
|
706
|
+
)
|
|
707
|
+
repo.git.execute(["git", "status"])
|
|
708
|
+
|
|
709
|
+
user_prompt = (
|
|
710
|
+
"You have local changes in this repository that are not part of a previous commit. "
|
|
711
|
+
"Do you still want to continue?"
|
|
712
|
+
)
|
|
713
|
+
if not policy.should_proceed(user_prompt):
|
|
714
|
+
if is_interactive:
|
|
715
|
+
console.message("Not creating a new version.")
|
|
716
|
+
raise typer.Exit(0)
|
|
717
|
+
else:
|
|
718
|
+
console.message(
|
|
719
|
+
"Cannot create a new version non-interactively without --force."
|
|
720
|
+
)
|
|
721
|
+
raise typer.Exit(1)
|
|
722
|
+
|
|
723
|
+
except InvalidGitRepositoryError:
|
|
724
|
+
pass # not a git repository, which is acceptable
|
|
725
|
+
|
|
726
|
+
@staticmethod
|
|
727
|
+
def get_existing_app_pkg_info(
|
|
728
|
+
package_name: str,
|
|
729
|
+
package_role: str,
|
|
730
|
+
) -> Optional[dict]:
|
|
731
|
+
"""
|
|
732
|
+
Check for an existing application package by the same name as in project definition, in account.
|
|
733
|
+
It executes a 'show application packages like' query and returns the result as single row, if one exists.
|
|
734
|
+
"""
|
|
735
|
+
sql_executor = get_sql_executor()
|
|
736
|
+
with sql_executor.use_role(package_role):
|
|
737
|
+
return sql_executor.show_specific_object(
|
|
738
|
+
"application packages", package_name, name_col=NAME_COL
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
@staticmethod
|
|
742
|
+
def get_app_pkg_distribution_in_snowflake(
|
|
743
|
+
package_name: str,
|
|
744
|
+
package_role: str,
|
|
745
|
+
) -> str:
|
|
746
|
+
"""
|
|
747
|
+
Returns the 'distribution' attribute of a 'describe application package' SQL query, in lowercase.
|
|
748
|
+
"""
|
|
749
|
+
sql_executor = get_sql_executor()
|
|
750
|
+
with sql_executor.use_role(package_role):
|
|
751
|
+
try:
|
|
752
|
+
desc_cursor = sql_executor.execute_query(
|
|
753
|
+
f"describe application package {package_name}"
|
|
754
|
+
)
|
|
755
|
+
except ProgrammingError as err:
|
|
756
|
+
generic_sql_error_handler(err)
|
|
757
|
+
|
|
758
|
+
if desc_cursor.rowcount is None or desc_cursor.rowcount == 0:
|
|
759
|
+
raise SnowflakeSQLExecutionError()
|
|
760
|
+
else:
|
|
761
|
+
for row in desc_cursor:
|
|
762
|
+
if row[0].lower() == "distribution":
|
|
763
|
+
return row[1].lower()
|
|
764
|
+
raise ProgrammingError(
|
|
765
|
+
msg=dedent(
|
|
766
|
+
f"""\
|
|
767
|
+
Could not find the 'distribution' attribute for application package {package_name} in the output of SQL query:
|
|
768
|
+
'describe application package {package_name}'
|
|
769
|
+
"""
|
|
770
|
+
)
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
@classmethod
|
|
774
|
+
def verify_project_distribution(
|
|
775
|
+
cls,
|
|
776
|
+
console: AbstractConsole,
|
|
777
|
+
package_name: str,
|
|
778
|
+
package_role: str,
|
|
779
|
+
package_distribution: str,
|
|
780
|
+
expected_distribution: Optional[str] = None,
|
|
781
|
+
) -> bool:
|
|
782
|
+
"""
|
|
783
|
+
Returns true if the 'distribution' attribute of an existing application package in snowflake
|
|
784
|
+
is the same as the the attribute specified in project definition file.
|
|
785
|
+
"""
|
|
786
|
+
actual_distribution = (
|
|
787
|
+
expected_distribution
|
|
788
|
+
if expected_distribution
|
|
789
|
+
else cls.get_app_pkg_distribution_in_snowflake(
|
|
790
|
+
package_name=package_name,
|
|
791
|
+
package_role=package_role,
|
|
792
|
+
)
|
|
793
|
+
)
|
|
794
|
+
project_def_distribution = package_distribution.lower()
|
|
795
|
+
if actual_distribution != project_def_distribution:
|
|
796
|
+
console.warning(
|
|
797
|
+
dedent(
|
|
798
|
+
f"""\
|
|
799
|
+
Application package {package_name} in your Snowflake account has distribution property {actual_distribution},
|
|
800
|
+
which does not match the value specified in project definition file: {project_def_distribution}.
|
|
801
|
+
"""
|
|
802
|
+
)
|
|
803
|
+
)
|
|
804
|
+
return False
|
|
805
|
+
return True
|
|
806
|
+
|
|
807
|
+
@staticmethod
|
|
808
|
+
@contextmanager
|
|
809
|
+
def use_package_warehouse(
|
|
810
|
+
package_warehouse: Optional[str],
|
|
811
|
+
):
|
|
812
|
+
if package_warehouse:
|
|
813
|
+
with get_sql_executor().use_warehouse(package_warehouse):
|
|
814
|
+
yield
|
|
815
|
+
else:
|
|
816
|
+
raise ClickException(
|
|
817
|
+
dedent(
|
|
818
|
+
f"""\
|
|
819
|
+
Application package warehouse cannot be empty.
|
|
820
|
+
Please provide a value for it in your connection information or your project definition file.
|
|
821
|
+
"""
|
|
822
|
+
)
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
@classmethod
|
|
826
|
+
def apply_package_scripts(
|
|
827
|
+
cls,
|
|
828
|
+
console: AbstractConsole,
|
|
829
|
+
package_scripts: List[str],
|
|
830
|
+
package_warehouse: Optional[str],
|
|
831
|
+
project_root: Path,
|
|
832
|
+
package_role: str,
|
|
833
|
+
package_name: str,
|
|
834
|
+
) -> None:
|
|
835
|
+
"""
|
|
836
|
+
Assuming the application package exists and we are using the correct role,
|
|
837
|
+
applies all package scripts in-order to the application package.
|
|
838
|
+
"""
|
|
839
|
+
|
|
840
|
+
if package_scripts:
|
|
841
|
+
console.warning(
|
|
842
|
+
"WARNING: native_app.package.scripts is deprecated. Please migrate to using native_app.package.post_deploy."
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
queued_queries = render_script_templates(
|
|
846
|
+
project_root,
|
|
847
|
+
dict(package_name=package_name),
|
|
848
|
+
package_scripts,
|
|
849
|
+
get_basic_jinja_env(),
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
# once we're sure all the templates expanded correctly, execute all of them
|
|
853
|
+
with cls.use_package_warehouse(
|
|
854
|
+
package_warehouse=package_warehouse,
|
|
855
|
+
):
|
|
856
|
+
try:
|
|
857
|
+
for i, queries in enumerate(queued_queries):
|
|
858
|
+
console.step(f"Applying package script: {package_scripts[i]}")
|
|
859
|
+
get_sql_executor().execute_queries(queries)
|
|
860
|
+
except ProgrammingError as err:
|
|
861
|
+
generic_sql_error_handler(
|
|
862
|
+
err, role=package_role, warehouse=package_warehouse
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
@classmethod
|
|
866
|
+
def create_app_package(
|
|
867
|
+
cls,
|
|
868
|
+
console: AbstractConsole,
|
|
869
|
+
package_name: str,
|
|
870
|
+
package_role: str,
|
|
871
|
+
package_distribution: str,
|
|
872
|
+
) -> None:
|
|
873
|
+
"""
|
|
874
|
+
Creates the application package with our up-to-date stage if none exists.
|
|
875
|
+
"""
|
|
876
|
+
|
|
877
|
+
# 1. Check for existing existing application package
|
|
878
|
+
show_obj_row = cls.get_existing_app_pkg_info(
|
|
879
|
+
package_name=package_name,
|
|
880
|
+
package_role=package_role,
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
if show_obj_row:
|
|
884
|
+
# 2. Check distribution of the existing application package
|
|
885
|
+
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
886
|
+
package_name=package_name,
|
|
887
|
+
package_role=package_role,
|
|
888
|
+
)
|
|
889
|
+
if not cls.verify_project_distribution(
|
|
890
|
+
console=console,
|
|
891
|
+
package_name=package_name,
|
|
892
|
+
package_role=package_role,
|
|
893
|
+
package_distribution=package_distribution,
|
|
894
|
+
expected_distribution=actual_distribution,
|
|
895
|
+
):
|
|
896
|
+
console.warning(
|
|
897
|
+
f"Continuing to execute `snow app run` on application package {package_name} with distribution '{actual_distribution}'."
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
# 3. If actual_distribution is external, skip comment check
|
|
901
|
+
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
902
|
+
row_comment = show_obj_row[COMMENT_COL]
|
|
903
|
+
|
|
904
|
+
if row_comment not in ALLOWED_SPECIAL_COMMENTS:
|
|
905
|
+
raise ApplicationPackageAlreadyExistsError(package_name)
|
|
906
|
+
|
|
907
|
+
return
|
|
908
|
+
|
|
909
|
+
# If no application package pre-exists, create an application package, with the specified distribution in the project definition file.
|
|
910
|
+
sql_executor = get_sql_executor()
|
|
911
|
+
with sql_executor.use_role(package_role):
|
|
912
|
+
console.step(f"Creating new application package {package_name} in account.")
|
|
913
|
+
sql_executor.execute_query(
|
|
914
|
+
dedent(
|
|
915
|
+
f"""\
|
|
916
|
+
create application package {package_name}
|
|
917
|
+
comment = {SPECIAL_COMMENT}
|
|
918
|
+
distribution = {package_distribution}
|
|
919
|
+
"""
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
@classmethod
|
|
924
|
+
def execute_post_deploy_hooks(
|
|
925
|
+
cls,
|
|
926
|
+
console: AbstractConsole,
|
|
927
|
+
project_root: Path,
|
|
928
|
+
post_deploy_hooks: Optional[List[PostDeployHook]],
|
|
929
|
+
package_name: str,
|
|
930
|
+
package_warehouse: Optional[str],
|
|
931
|
+
):
|
|
932
|
+
with cls.use_package_warehouse(package_warehouse):
|
|
933
|
+
execute_post_deploy_hooks(
|
|
934
|
+
console=console,
|
|
935
|
+
project_root=project_root,
|
|
936
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
937
|
+
deployed_object_type="application package",
|
|
938
|
+
database_name=package_name,
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
@classmethod
|
|
942
|
+
def validate_setup_script(
|
|
943
|
+
cls,
|
|
944
|
+
console: AbstractConsole,
|
|
945
|
+
package_name: str,
|
|
946
|
+
package_role: str,
|
|
947
|
+
stage_fqn: str,
|
|
948
|
+
use_scratch_stage: bool,
|
|
949
|
+
scratch_stage_fqn: str,
|
|
950
|
+
deploy_to_scratch_stage_fn: Callable,
|
|
951
|
+
):
|
|
952
|
+
"""Validates Native App setup script SQL."""
|
|
953
|
+
with console.phase(f"Validating Snowflake Native App setup script."):
|
|
954
|
+
validation_result = cls.get_validation_result(
|
|
955
|
+
console=console,
|
|
956
|
+
package_name=package_name,
|
|
957
|
+
package_role=package_role,
|
|
958
|
+
stage_fqn=stage_fqn,
|
|
959
|
+
use_scratch_stage=use_scratch_stage,
|
|
960
|
+
scratch_stage_fqn=scratch_stage_fqn,
|
|
961
|
+
deploy_to_scratch_stage_fn=deploy_to_scratch_stage_fn,
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
# First print warnings, regardless of the outcome of validation
|
|
965
|
+
for warning in validation_result.get("warnings", []):
|
|
966
|
+
console.warning(validation_item_to_str(warning))
|
|
967
|
+
|
|
968
|
+
# Then print errors
|
|
969
|
+
for error in validation_result.get("errors", []):
|
|
970
|
+
# Print them as warnings for now since we're going to be
|
|
971
|
+
# revamping CLI output soon
|
|
972
|
+
console.warning(validation_item_to_str(error))
|
|
973
|
+
|
|
974
|
+
# Then raise an exception if validation failed
|
|
975
|
+
if validation_result["status"] == "FAIL":
|
|
976
|
+
raise SetupScriptFailedValidation()
|
|
977
|
+
|
|
978
|
+
@staticmethod
|
|
979
|
+
def get_validation_result(
|
|
980
|
+
console: AbstractConsole,
|
|
981
|
+
package_name: str,
|
|
982
|
+
package_role: str,
|
|
983
|
+
stage_fqn: str,
|
|
984
|
+
use_scratch_stage: bool,
|
|
985
|
+
scratch_stage_fqn: str,
|
|
986
|
+
deploy_to_scratch_stage_fn: Callable,
|
|
987
|
+
):
|
|
988
|
+
"""Call system$validate_native_app_setup() to validate deployed Native App setup script."""
|
|
989
|
+
if use_scratch_stage:
|
|
990
|
+
stage_fqn = scratch_stage_fqn
|
|
991
|
+
deploy_to_scratch_stage_fn()
|
|
992
|
+
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
|
|
993
|
+
sql_executor = get_sql_executor()
|
|
994
|
+
try:
|
|
995
|
+
cursor = sql_executor.execute_query(
|
|
996
|
+
f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
|
|
997
|
+
)
|
|
998
|
+
except ProgrammingError as err:
|
|
999
|
+
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
1000
|
+
raise ApplicationPackageDoesNotExistError(package_name)
|
|
1001
|
+
generic_sql_error_handler(err)
|
|
1002
|
+
else:
|
|
1003
|
+
if not cursor.rowcount:
|
|
1004
|
+
raise SnowflakeSQLExecutionError()
|
|
1005
|
+
return json.loads(cursor.fetchone()[0])
|
|
1006
|
+
finally:
|
|
1007
|
+
if use_scratch_stage:
|
|
1008
|
+
console.step(f"Dropping stage {scratch_stage_fqn}.")
|
|
1009
|
+
with sql_executor.use_role(package_role):
|
|
1010
|
+
sql_executor.execute_query(
|
|
1011
|
+
f"drop stage if exists {scratch_stage_fqn}"
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
@classmethod
|
|
1015
|
+
def drop(
|
|
1016
|
+
cls,
|
|
1017
|
+
console: AbstractConsole,
|
|
1018
|
+
package_name: str,
|
|
1019
|
+
package_role: str,
|
|
1020
|
+
force_drop: bool,
|
|
1021
|
+
):
|
|
1022
|
+
sql_executor = get_sql_executor()
|
|
1023
|
+
needs_confirm = True
|
|
1024
|
+
|
|
1025
|
+
# 1. If existing application package is not found, exit gracefully
|
|
1026
|
+
show_obj_row = cls.get_existing_app_pkg_info(
|
|
1027
|
+
package_name=package_name,
|
|
1028
|
+
package_role=package_role,
|
|
1029
|
+
)
|
|
1030
|
+
if show_obj_row is None:
|
|
1031
|
+
console.warning(
|
|
1032
|
+
f"Role {package_role} does not own any application package with the name {package_name}, or the application package does not exist."
|
|
1033
|
+
)
|
|
1034
|
+
return
|
|
1035
|
+
|
|
1036
|
+
with sql_executor.use_role(package_role):
|
|
1037
|
+
# 2. Check for versions in the application package
|
|
1038
|
+
show_versions_query = f"show versions in application package {package_name}"
|
|
1039
|
+
show_versions_cursor = sql_executor.execute_query(
|
|
1040
|
+
show_versions_query, cursor_class=DictCursor
|
|
1041
|
+
)
|
|
1042
|
+
if show_versions_cursor.rowcount is None:
|
|
1043
|
+
raise SnowflakeSQLExecutionError(show_versions_query)
|
|
1044
|
+
|
|
1045
|
+
if show_versions_cursor.rowcount > 0:
|
|
1046
|
+
# allow dropping a package with versions when --force is set
|
|
1047
|
+
if not force_drop:
|
|
1048
|
+
raise CouldNotDropApplicationPackageWithVersions(
|
|
1049
|
+
"Drop versions first, or use --force to override."
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
# 3. Check distribution of the existing application package
|
|
1053
|
+
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
1054
|
+
package_name=package_name,
|
|
1055
|
+
package_role=package_role,
|
|
1056
|
+
)
|
|
1057
|
+
if not cls.verify_project_distribution(
|
|
1058
|
+
console=console,
|
|
1059
|
+
package_name=package_name,
|
|
1060
|
+
package_role=package_role,
|
|
1061
|
+
package_distribution=actual_distribution,
|
|
1062
|
+
):
|
|
1063
|
+
console.warning(
|
|
1064
|
+
f"Dropping application package {package_name} with distribution '{actual_distribution}'."
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
# 4. If distribution is internal, check if created by the Snowflake CLI
|
|
1068
|
+
row_comment = show_obj_row[COMMENT_COL]
|
|
1069
|
+
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
1070
|
+
if row_comment in ALLOWED_SPECIAL_COMMENTS:
|
|
1071
|
+
needs_confirm = False
|
|
1072
|
+
else:
|
|
1073
|
+
if needs_confirmation(needs_confirm, force_drop):
|
|
1074
|
+
console.warning(
|
|
1075
|
+
f"Application package {package_name} was not created by Snowflake CLI."
|
|
1076
|
+
)
|
|
1077
|
+
else:
|
|
1078
|
+
if needs_confirmation(needs_confirm, force_drop):
|
|
1079
|
+
console.warning(
|
|
1080
|
+
f"Application package {package_name} in your Snowflake account has distribution property '{EXTERNAL_DISTRIBUTION}' and could be associated with one or more of your listings on Snowflake Marketplace."
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
if needs_confirmation(needs_confirm, force_drop):
|
|
1084
|
+
should_drop_object = typer.confirm(
|
|
1085
|
+
dedent(
|
|
1086
|
+
f"""\
|
|
1087
|
+
Application package details:
|
|
1088
|
+
Name: {package_name}
|
|
1089
|
+
Created on: {show_obj_row["created_on"]}
|
|
1090
|
+
Distribution: {actual_distribution}
|
|
1091
|
+
Owner: {show_obj_row[OWNER_COL]}
|
|
1092
|
+
Comment: {show_obj_row[COMMENT_COL]}
|
|
1093
|
+
Are you sure you want to drop it?
|
|
1094
|
+
"""
|
|
1095
|
+
)
|
|
1096
|
+
)
|
|
1097
|
+
if not should_drop_object:
|
|
1098
|
+
console.message(f"Did not drop application package {package_name}.")
|
|
1099
|
+
return # The user desires to keep the application package, therefore exit gracefully
|
|
1100
|
+
|
|
1101
|
+
# All validations have passed, drop object
|
|
1102
|
+
drop_generic_object(
|
|
1103
|
+
console=console,
|
|
1104
|
+
object_type="application package",
|
|
1105
|
+
object_name=package_name,
|
|
1106
|
+
role=package_role,
|
|
1107
|
+
)
|