snowflake-cli-labs 3.0.0rc2__py3-none-any.whl → 3.0.0rc4__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/commands_registration/builtin_plugins.py +2 -0
- snowflake/cli/_app/secret.py +9 -0
- snowflake/cli/_app/snow_connector.py +39 -27
- snowflake/cli/_app/telemetry.py +28 -0
- snowflake/cli/_plugins/connection/commands.py +9 -4
- snowflake/cli/_plugins/git/manager.py +53 -7
- snowflake/cli/_plugins/helpers/commands.py +61 -0
- snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
- 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 +6 -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 +5 -1
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +4 -1
- snowflake/cli/_plugins/nativeapp/commands.py +87 -96
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/{api/entities/application_entity.py → _plugins/nativeapp/entities/application.py} +264 -83
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1392 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
- snowflake/cli/_plugins/nativeapp/manager.py +69 -185
- snowflake/cli/_plugins/nativeapp/policy.py +3 -0
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
- snowflake/cli/_plugins/nativeapp/run_processor.py +17 -20
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +4 -6
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +122 -88
- snowflake/cli/_plugins/nativeapp/version/commands.py +7 -39
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +46 -312
- snowflake/cli/_plugins/object/manager.py +36 -15
- snowflake/cli/_plugins/snowpark/commands.py +4 -4
- snowflake/cli/_plugins/snowpark/common.py +4 -4
- 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 +15 -3
- snowflake/cli/_plugins/streamlit/manager.py +12 -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/commands.py +116 -36
- snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
- snowflake/cli/api/cli_global_context.py +7 -0
- snowflake/cli/api/commands/decorators.py +14 -0
- snowflake/cli/api/commands/flags.py +18 -0
- snowflake/cli/api/commands/snow_typer.py +1 -1
- snowflake/cli/api/config.py +25 -6
- snowflake/cli/api/connections.py +3 -1
- snowflake/cli/api/entities/common.py +4 -0
- snowflake/cli/api/entities/utils.py +3 -14
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/identifiers.py +4 -3
- snowflake/cli/api/metrics.py +92 -0
- snowflake/cli/api/project/definition_conversion.py +61 -18
- snowflake/cli/api/project/schemas/entities/common.py +17 -4
- snowflake/cli/api/project/schemas/entities/entities.py +11 -10
- snowflake/cli/api/project/schemas/project_definition.py +5 -7
- 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/sql_templates.py +6 -0
- snowflake/cli/api/rest_api.py +11 -5
- snowflake/cli/api/sql_execution.py +6 -15
- snowflake/cli/api/utils/definition_rendering.py +24 -4
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/METADATA +9 -7
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/RECORD +83 -79
- snowflake/cli/_plugins/nativeapp/init.py +0 -345
- snowflake/cli/api/entities/application_package_entity.py +0 -658
- snowflake/cli/api/project/schemas/entities/application_entity_model.py +0 -56
- snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +0 -94
- 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.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1392 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
from typing import List, Literal, Optional, Union
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from click import BadOptionUsage, ClickException
|
|
11
|
+
from pydantic import Field, field_validator
|
|
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 needs_confirmation
|
|
43
|
+
from snowflake.cli._plugins.stage.diff import DiffResult
|
|
44
|
+
from snowflake.cli._plugins.stage.manager import StageManager
|
|
45
|
+
from snowflake.cli._plugins.workspace.action_context import ActionContext
|
|
46
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
47
|
+
from snowflake.cli.api.console.abc import AbstractConsole
|
|
48
|
+
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
49
|
+
from snowflake.cli.api.entities.utils import (
|
|
50
|
+
drop_generic_object,
|
|
51
|
+
execute_post_deploy_hooks,
|
|
52
|
+
generic_sql_error_handler,
|
|
53
|
+
render_script_templates,
|
|
54
|
+
sync_deploy_root_with_stage,
|
|
55
|
+
validation_item_to_str,
|
|
56
|
+
)
|
|
57
|
+
from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
|
|
58
|
+
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
59
|
+
from snowflake.cli.api.metrics import CLICounterField
|
|
60
|
+
from snowflake.cli.api.project.schemas.entities.common import (
|
|
61
|
+
EntityModelBase,
|
|
62
|
+
Identifier,
|
|
63
|
+
PostDeployHook,
|
|
64
|
+
)
|
|
65
|
+
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
66
|
+
DiscriminatorField,
|
|
67
|
+
IdentifierField,
|
|
68
|
+
)
|
|
69
|
+
from snowflake.cli.api.project.schemas.v1.native_app.package import DistributionOptions
|
|
70
|
+
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
71
|
+
from snowflake.cli.api.project.util import (
|
|
72
|
+
append_test_resource_suffix,
|
|
73
|
+
extract_schema,
|
|
74
|
+
identifier_to_show_like_pattern,
|
|
75
|
+
to_identifier,
|
|
76
|
+
unquote_identifier,
|
|
77
|
+
)
|
|
78
|
+
from snowflake.cli.api.rendering.jinja import get_basic_jinja_env
|
|
79
|
+
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
80
|
+
from snowflake.connector import DictCursor, ProgrammingError
|
|
81
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ApplicationPackageEntityModel(EntityModelBase):
|
|
85
|
+
type: Literal["application package"] = DiscriminatorField() # noqa: A003
|
|
86
|
+
artifacts: List[Union[PathMapping, str]] = Field(
|
|
87
|
+
title="List of paths or file source/destination pairs to add to the deploy root",
|
|
88
|
+
)
|
|
89
|
+
bundle_root: Optional[str] = Field(
|
|
90
|
+
title="Folder at the root of your project where artifacts necessary to perform the bundle step are stored.",
|
|
91
|
+
default="output/bundle/",
|
|
92
|
+
)
|
|
93
|
+
deploy_root: Optional[str] = Field(
|
|
94
|
+
title="Folder at the root of your project where the build step copies the artifacts",
|
|
95
|
+
default="output/deploy/",
|
|
96
|
+
)
|
|
97
|
+
generated_root: Optional[str] = Field(
|
|
98
|
+
title="Subdirectory of the deploy root where files generated by the Snowflake CLI will be written.",
|
|
99
|
+
default="__generated/",
|
|
100
|
+
)
|
|
101
|
+
stage: Optional[str] = IdentifierField(
|
|
102
|
+
title="Identifier of the stage that stores the application artifacts.",
|
|
103
|
+
default="app_src.stage",
|
|
104
|
+
)
|
|
105
|
+
scratch_stage: Optional[str] = IdentifierField(
|
|
106
|
+
title="Identifier of the stage that stores temporary scratch data used by the Snowflake CLI.",
|
|
107
|
+
default="app_src.stage_snowflake_cli_scratch",
|
|
108
|
+
)
|
|
109
|
+
distribution: Optional[DistributionOptions] = Field(
|
|
110
|
+
title="Distribution of the application package created by the Snowflake CLI",
|
|
111
|
+
default="internal",
|
|
112
|
+
)
|
|
113
|
+
manifest: str = Field(
|
|
114
|
+
title="Path to manifest.yml",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@field_validator("identifier")
|
|
118
|
+
@classmethod
|
|
119
|
+
def append_test_resource_suffix_to_identifier(
|
|
120
|
+
cls, input_value: Identifier | str
|
|
121
|
+
) -> Identifier | str:
|
|
122
|
+
identifier = (
|
|
123
|
+
input_value.name if isinstance(input_value, Identifier) else input_value
|
|
124
|
+
)
|
|
125
|
+
with_suffix = append_test_resource_suffix(identifier)
|
|
126
|
+
if isinstance(input_value, Identifier):
|
|
127
|
+
return input_value.model_copy(update=dict(name=with_suffix))
|
|
128
|
+
return with_suffix
|
|
129
|
+
|
|
130
|
+
@field_validator("artifacts")
|
|
131
|
+
@classmethod
|
|
132
|
+
def transform_artifacts(
|
|
133
|
+
cls, orig_artifacts: List[Union[PathMapping, str]]
|
|
134
|
+
) -> List[PathMapping]:
|
|
135
|
+
transformed_artifacts = []
|
|
136
|
+
if orig_artifacts is None:
|
|
137
|
+
return transformed_artifacts
|
|
138
|
+
|
|
139
|
+
for artifact in orig_artifacts:
|
|
140
|
+
if isinstance(artifact, PathMapping):
|
|
141
|
+
transformed_artifacts.append(artifact)
|
|
142
|
+
else:
|
|
143
|
+
transformed_artifacts.append(PathMapping(src=artifact))
|
|
144
|
+
|
|
145
|
+
return transformed_artifacts
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
149
|
+
"""
|
|
150
|
+
A Native App application package.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def action_bundle(self, ctx: ActionContext, *args, **kwargs):
|
|
154
|
+
model = self._entity_model
|
|
155
|
+
return self.bundle(
|
|
156
|
+
project_root=ctx.project_root,
|
|
157
|
+
deploy_root=Path(model.deploy_root),
|
|
158
|
+
bundle_root=Path(model.bundle_root),
|
|
159
|
+
generated_root=Path(model.generated_root),
|
|
160
|
+
package_name=model.identifier,
|
|
161
|
+
artifacts=model.artifacts,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def action_deploy(
|
|
165
|
+
self,
|
|
166
|
+
ctx: ActionContext,
|
|
167
|
+
prune: bool,
|
|
168
|
+
recursive: bool,
|
|
169
|
+
paths: List[Path],
|
|
170
|
+
validate: bool,
|
|
171
|
+
interactive: bool,
|
|
172
|
+
force: bool,
|
|
173
|
+
stage_fqn: Optional[str] = None,
|
|
174
|
+
*args,
|
|
175
|
+
**kwargs,
|
|
176
|
+
):
|
|
177
|
+
model = self._entity_model
|
|
178
|
+
package_name = model.fqn.identifier
|
|
179
|
+
|
|
180
|
+
if force:
|
|
181
|
+
policy = AllowAlwaysPolicy()
|
|
182
|
+
elif interactive:
|
|
183
|
+
policy = AskAlwaysPolicy()
|
|
184
|
+
else:
|
|
185
|
+
policy = DenyAlwaysPolicy()
|
|
186
|
+
|
|
187
|
+
return self.deploy(
|
|
188
|
+
console=ctx.console,
|
|
189
|
+
project_root=ctx.project_root,
|
|
190
|
+
deploy_root=Path(model.deploy_root),
|
|
191
|
+
bundle_root=Path(model.bundle_root),
|
|
192
|
+
generated_root=Path(model.generated_root),
|
|
193
|
+
artifacts=model.artifacts,
|
|
194
|
+
bundle_map=None,
|
|
195
|
+
package_name=package_name,
|
|
196
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
197
|
+
package_distribution=model.distribution,
|
|
198
|
+
prune=prune,
|
|
199
|
+
recursive=recursive,
|
|
200
|
+
paths=paths,
|
|
201
|
+
print_diff=True,
|
|
202
|
+
validate=validate,
|
|
203
|
+
stage_fqn=stage_fqn or f"{package_name}.{model.stage}",
|
|
204
|
+
package_warehouse=(
|
|
205
|
+
(model.meta and model.meta.warehouse) or ctx.default_warehouse
|
|
206
|
+
),
|
|
207
|
+
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
208
|
+
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
209
|
+
policy=policy,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def action_drop(self, ctx: ActionContext, force_drop: bool, *args, **kwargs):
|
|
213
|
+
model = self._entity_model
|
|
214
|
+
package_name = model.fqn.identifier
|
|
215
|
+
if model.meta and model.meta.role:
|
|
216
|
+
package_role = model.meta.role
|
|
217
|
+
else:
|
|
218
|
+
package_role = ctx.default_role
|
|
219
|
+
|
|
220
|
+
self.drop(
|
|
221
|
+
console=ctx.console,
|
|
222
|
+
package_name=package_name,
|
|
223
|
+
package_role=package_role,
|
|
224
|
+
force_drop=force_drop,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def action_validate(
|
|
228
|
+
self, ctx: ActionContext, interactive: bool, force: bool, *args, **kwargs
|
|
229
|
+
):
|
|
230
|
+
model = self._entity_model
|
|
231
|
+
package_name = model.fqn.identifier
|
|
232
|
+
if force:
|
|
233
|
+
policy = AllowAlwaysPolicy()
|
|
234
|
+
elif interactive:
|
|
235
|
+
policy = AskAlwaysPolicy()
|
|
236
|
+
else:
|
|
237
|
+
policy = DenyAlwaysPolicy()
|
|
238
|
+
|
|
239
|
+
self.validate_setup_script(
|
|
240
|
+
console=ctx.console,
|
|
241
|
+
project_root=ctx.project_root,
|
|
242
|
+
deploy_root=Path(model.deploy_root),
|
|
243
|
+
bundle_root=Path(model.bundle_root),
|
|
244
|
+
generated_root=Path(model.generated_root),
|
|
245
|
+
artifacts=model.artifacts,
|
|
246
|
+
package_name=package_name,
|
|
247
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
248
|
+
package_distribution=model.distribution,
|
|
249
|
+
prune=True,
|
|
250
|
+
recursive=True,
|
|
251
|
+
paths=[],
|
|
252
|
+
stage_fqn=f"{package_name}.{model.stage}",
|
|
253
|
+
package_warehouse=(
|
|
254
|
+
(model.meta and model.meta.warehouse) or ctx.default_warehouse
|
|
255
|
+
),
|
|
256
|
+
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
257
|
+
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
258
|
+
policy=policy,
|
|
259
|
+
use_scratch_stage=True,
|
|
260
|
+
scratch_stage_fqn=f"{package_name}.{model.scratch_stage}",
|
|
261
|
+
)
|
|
262
|
+
ctx.console.message("Setup script is valid")
|
|
263
|
+
|
|
264
|
+
def action_version_list(
|
|
265
|
+
self, ctx: ActionContext, *args, **kwargs
|
|
266
|
+
) -> SnowflakeCursor:
|
|
267
|
+
model = self._entity_model
|
|
268
|
+
return self.version_list(
|
|
269
|
+
package_name=model.fqn.identifier,
|
|
270
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def action_version_create(
|
|
274
|
+
self,
|
|
275
|
+
ctx: ActionContext,
|
|
276
|
+
version: Optional[str],
|
|
277
|
+
patch: Optional[int],
|
|
278
|
+
skip_git_check: bool,
|
|
279
|
+
interactive: bool,
|
|
280
|
+
force: bool,
|
|
281
|
+
*args,
|
|
282
|
+
**kwargs,
|
|
283
|
+
):
|
|
284
|
+
model = self._entity_model
|
|
285
|
+
package_name = model.fqn.identifier
|
|
286
|
+
return self.version_create(
|
|
287
|
+
console=ctx.console,
|
|
288
|
+
project_root=ctx.project_root,
|
|
289
|
+
deploy_root=Path(model.deploy_root),
|
|
290
|
+
bundle_root=Path(model.bundle_root),
|
|
291
|
+
generated_root=Path(model.generated_root),
|
|
292
|
+
artifacts=model.artifacts,
|
|
293
|
+
package_name=package_name,
|
|
294
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
295
|
+
package_distribution=model.distribution,
|
|
296
|
+
prune=True,
|
|
297
|
+
recursive=True,
|
|
298
|
+
paths=None,
|
|
299
|
+
print_diff=True,
|
|
300
|
+
validate=True,
|
|
301
|
+
stage_fqn=f"{package_name}.{model.stage}",
|
|
302
|
+
package_warehouse=(
|
|
303
|
+
(model.meta and model.meta.warehouse) or ctx.default_warehouse
|
|
304
|
+
),
|
|
305
|
+
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
306
|
+
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
307
|
+
version=version,
|
|
308
|
+
patch=patch,
|
|
309
|
+
skip_git_check=skip_git_check,
|
|
310
|
+
force=force,
|
|
311
|
+
interactive=interactive,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def action_version_drop(
|
|
315
|
+
self,
|
|
316
|
+
ctx: ActionContext,
|
|
317
|
+
version: Optional[str],
|
|
318
|
+
interactive: bool,
|
|
319
|
+
force: bool,
|
|
320
|
+
*args,
|
|
321
|
+
**kwargs,
|
|
322
|
+
):
|
|
323
|
+
model = self._entity_model
|
|
324
|
+
package_name = model.fqn.identifier
|
|
325
|
+
return self.version_drop(
|
|
326
|
+
console=ctx.console,
|
|
327
|
+
project_root=ctx.project_root,
|
|
328
|
+
deploy_root=Path(model.deploy_root),
|
|
329
|
+
bundle_root=Path(model.bundle_root),
|
|
330
|
+
generated_root=Path(model.generated_root),
|
|
331
|
+
artifacts=model.artifacts,
|
|
332
|
+
package_name=package_name,
|
|
333
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
334
|
+
package_distribution=model.distribution,
|
|
335
|
+
version=version,
|
|
336
|
+
force=force,
|
|
337
|
+
interactive=interactive,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def bundle(
|
|
342
|
+
project_root: Path,
|
|
343
|
+
deploy_root: Path,
|
|
344
|
+
bundle_root: Path,
|
|
345
|
+
generated_root: Path,
|
|
346
|
+
artifacts: list[PathMapping],
|
|
347
|
+
package_name: str,
|
|
348
|
+
):
|
|
349
|
+
bundle_map = build_bundle(project_root, deploy_root, artifacts)
|
|
350
|
+
bundle_context = BundleContext(
|
|
351
|
+
package_name=package_name,
|
|
352
|
+
artifacts=artifacts,
|
|
353
|
+
project_root=project_root,
|
|
354
|
+
bundle_root=bundle_root,
|
|
355
|
+
deploy_root=deploy_root,
|
|
356
|
+
generated_root=generated_root,
|
|
357
|
+
)
|
|
358
|
+
compiler = NativeAppCompiler(bundle_context)
|
|
359
|
+
compiler.compile_artifacts()
|
|
360
|
+
return bundle_map
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def deploy(
|
|
364
|
+
cls,
|
|
365
|
+
console: AbstractConsole,
|
|
366
|
+
project_root: Path,
|
|
367
|
+
deploy_root: Path,
|
|
368
|
+
bundle_root: Path,
|
|
369
|
+
generated_root: Path,
|
|
370
|
+
artifacts: list[PathMapping],
|
|
371
|
+
bundle_map: BundleMap | None,
|
|
372
|
+
package_name: str,
|
|
373
|
+
package_role: str,
|
|
374
|
+
package_distribution: str,
|
|
375
|
+
package_warehouse: str | None,
|
|
376
|
+
prune: bool,
|
|
377
|
+
recursive: bool,
|
|
378
|
+
paths: List[Path] | None,
|
|
379
|
+
print_diff: bool,
|
|
380
|
+
validate: bool,
|
|
381
|
+
stage_fqn: str,
|
|
382
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
383
|
+
package_scripts: List[str],
|
|
384
|
+
policy: PolicyBase,
|
|
385
|
+
) -> DiffResult:
|
|
386
|
+
# 1. Create a bundle if one wasn't passed in
|
|
387
|
+
bundle_map = bundle_map or cls.bundle(
|
|
388
|
+
project_root=project_root,
|
|
389
|
+
deploy_root=deploy_root,
|
|
390
|
+
bundle_root=bundle_root,
|
|
391
|
+
generated_root=generated_root,
|
|
392
|
+
artifacts=artifacts,
|
|
393
|
+
package_name=package_name,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# 2. Create an empty application package, if none exists
|
|
397
|
+
try:
|
|
398
|
+
cls.create_app_package(
|
|
399
|
+
console=console,
|
|
400
|
+
package_name=package_name,
|
|
401
|
+
package_role=package_role,
|
|
402
|
+
package_distribution=package_distribution,
|
|
403
|
+
)
|
|
404
|
+
except ApplicationPackageAlreadyExistsError as e:
|
|
405
|
+
console.warning(e.message)
|
|
406
|
+
if not policy.should_proceed("Proceed with using this package?"):
|
|
407
|
+
raise typer.Abort() from e
|
|
408
|
+
with get_sql_executor().use_role(package_role):
|
|
409
|
+
cls.apply_package_scripts(
|
|
410
|
+
console=console,
|
|
411
|
+
package_scripts=package_scripts,
|
|
412
|
+
package_warehouse=package_warehouse,
|
|
413
|
+
project_root=project_root,
|
|
414
|
+
package_role=package_role,
|
|
415
|
+
package_name=package_name,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# 3. Upload files from deploy root local folder to the above stage
|
|
419
|
+
stage_schema = extract_schema(stage_fqn)
|
|
420
|
+
diff = sync_deploy_root_with_stage(
|
|
421
|
+
console=console,
|
|
422
|
+
deploy_root=deploy_root,
|
|
423
|
+
package_name=package_name,
|
|
424
|
+
stage_schema=stage_schema,
|
|
425
|
+
bundle_map=bundle_map,
|
|
426
|
+
role=package_role,
|
|
427
|
+
prune=prune,
|
|
428
|
+
recursive=recursive,
|
|
429
|
+
stage_fqn=stage_fqn,
|
|
430
|
+
local_paths_to_sync=paths,
|
|
431
|
+
print_diff=print_diff,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
cls.execute_post_deploy_hooks(
|
|
435
|
+
console=console,
|
|
436
|
+
project_root=project_root,
|
|
437
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
438
|
+
package_name=package_name,
|
|
439
|
+
package_warehouse=package_warehouse,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if validate:
|
|
443
|
+
cls.validate_setup_script(
|
|
444
|
+
console=console,
|
|
445
|
+
project_root=project_root,
|
|
446
|
+
deploy_root=deploy_root,
|
|
447
|
+
bundle_root=bundle_root,
|
|
448
|
+
generated_root=generated_root,
|
|
449
|
+
artifacts=artifacts,
|
|
450
|
+
package_name=package_name,
|
|
451
|
+
package_role=package_role,
|
|
452
|
+
package_distribution=package_distribution,
|
|
453
|
+
prune=prune,
|
|
454
|
+
recursive=recursive,
|
|
455
|
+
paths=paths,
|
|
456
|
+
stage_fqn=stage_fqn,
|
|
457
|
+
package_warehouse=package_warehouse,
|
|
458
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
459
|
+
package_scripts=package_scripts,
|
|
460
|
+
policy=policy,
|
|
461
|
+
use_scratch_stage=False,
|
|
462
|
+
scratch_stage_fqn="",
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return diff
|
|
466
|
+
|
|
467
|
+
@staticmethod
|
|
468
|
+
def version_list(package_name: str, package_role: str) -> SnowflakeCursor:
|
|
469
|
+
"""
|
|
470
|
+
Get all existing versions, if defined, for an application package.
|
|
471
|
+
It executes a 'show versions in application package' query and returns all the results.
|
|
472
|
+
"""
|
|
473
|
+
sql_executor = get_sql_executor()
|
|
474
|
+
with sql_executor.use_role(package_role):
|
|
475
|
+
show_obj_query = f"show versions in application package {package_name}"
|
|
476
|
+
show_obj_cursor = sql_executor.execute_query(show_obj_query)
|
|
477
|
+
|
|
478
|
+
if show_obj_cursor.rowcount is None:
|
|
479
|
+
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
480
|
+
|
|
481
|
+
return show_obj_cursor
|
|
482
|
+
|
|
483
|
+
@classmethod
|
|
484
|
+
def version_create(
|
|
485
|
+
cls,
|
|
486
|
+
console: AbstractConsole,
|
|
487
|
+
project_root: Path,
|
|
488
|
+
deploy_root: Path,
|
|
489
|
+
bundle_root: Path,
|
|
490
|
+
generated_root: Path,
|
|
491
|
+
artifacts: list[PathMapping],
|
|
492
|
+
package_name: str,
|
|
493
|
+
package_role: str,
|
|
494
|
+
package_distribution: str,
|
|
495
|
+
package_warehouse: str | None,
|
|
496
|
+
prune: bool,
|
|
497
|
+
recursive: bool,
|
|
498
|
+
paths: List[Path] | None,
|
|
499
|
+
print_diff: bool,
|
|
500
|
+
validate: bool,
|
|
501
|
+
stage_fqn: str,
|
|
502
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
503
|
+
package_scripts: List[str],
|
|
504
|
+
version: Optional[str],
|
|
505
|
+
patch: Optional[int],
|
|
506
|
+
force: bool,
|
|
507
|
+
interactive: bool,
|
|
508
|
+
skip_git_check: bool,
|
|
509
|
+
):
|
|
510
|
+
"""
|
|
511
|
+
Perform bundle, application package creation, stage upload, version and/or patch to an application package.
|
|
512
|
+
"""
|
|
513
|
+
is_interactive = False
|
|
514
|
+
if force:
|
|
515
|
+
policy = AllowAlwaysPolicy()
|
|
516
|
+
elif interactive:
|
|
517
|
+
is_interactive = True
|
|
518
|
+
policy = AskAlwaysPolicy()
|
|
519
|
+
else:
|
|
520
|
+
policy = DenyAlwaysPolicy()
|
|
521
|
+
|
|
522
|
+
if skip_git_check:
|
|
523
|
+
git_policy = DenyAlwaysPolicy()
|
|
524
|
+
else:
|
|
525
|
+
git_policy = AllowAlwaysPolicy()
|
|
526
|
+
|
|
527
|
+
# Make sure version is not None before proceeding any further.
|
|
528
|
+
# This will raise an exception if version information is not found. Patch can be None.
|
|
529
|
+
bundle_map = None
|
|
530
|
+
if not version:
|
|
531
|
+
console.message(
|
|
532
|
+
dedent(
|
|
533
|
+
f"""\
|
|
534
|
+
Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
|
|
535
|
+
This step will bundle your app artifacts to determine the location of the manifest.yml file.
|
|
536
|
+
"""
|
|
537
|
+
)
|
|
538
|
+
)
|
|
539
|
+
bundle_map = cls.bundle(
|
|
540
|
+
project_root=project_root,
|
|
541
|
+
deploy_root=deploy_root,
|
|
542
|
+
bundle_root=bundle_root,
|
|
543
|
+
generated_root=generated_root,
|
|
544
|
+
artifacts=artifacts,
|
|
545
|
+
package_name=package_name,
|
|
546
|
+
)
|
|
547
|
+
version, patch = find_version_info_in_manifest_file(deploy_root)
|
|
548
|
+
if not version:
|
|
549
|
+
raise ClickException(
|
|
550
|
+
"Manifest.yml file does not contain a value for the version field."
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# Check if --patch needs to throw a bad option error, either if application package does not exist or if version does not exist
|
|
554
|
+
if patch is not None:
|
|
555
|
+
try:
|
|
556
|
+
if not cls.get_existing_version_info(
|
|
557
|
+
version, package_name, package_role
|
|
558
|
+
):
|
|
559
|
+
raise BadOptionUsage(
|
|
560
|
+
option_name="patch",
|
|
561
|
+
message=f"Cannot create a custom patch when version {version} is not defined in the application package {package_name}. Try again without using --patch.",
|
|
562
|
+
)
|
|
563
|
+
except ApplicationPackageDoesNotExistError as app_err:
|
|
564
|
+
raise BadOptionUsage(
|
|
565
|
+
option_name="patch",
|
|
566
|
+
message=f"Cannot create a custom patch when application package {package_name} does not exist. Try again without using --patch.",
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
if git_policy.should_proceed():
|
|
570
|
+
cls.check_index_changes_in_git_repo(
|
|
571
|
+
console=console,
|
|
572
|
+
project_root=project_root,
|
|
573
|
+
policy=policy,
|
|
574
|
+
is_interactive=is_interactive,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
cls.deploy(
|
|
578
|
+
console=console,
|
|
579
|
+
project_root=project_root,
|
|
580
|
+
deploy_root=deploy_root,
|
|
581
|
+
bundle_root=bundle_root,
|
|
582
|
+
generated_root=generated_root,
|
|
583
|
+
artifacts=artifacts,
|
|
584
|
+
bundle_map=bundle_map,
|
|
585
|
+
package_name=package_name,
|
|
586
|
+
package_role=package_role,
|
|
587
|
+
package_distribution=package_distribution,
|
|
588
|
+
prune=prune,
|
|
589
|
+
recursive=recursive,
|
|
590
|
+
paths=paths,
|
|
591
|
+
print_diff=print_diff,
|
|
592
|
+
validate=validate,
|
|
593
|
+
stage_fqn=stage_fqn,
|
|
594
|
+
package_warehouse=package_warehouse,
|
|
595
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
596
|
+
package_scripts=package_scripts,
|
|
597
|
+
policy=policy,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# Warn if the version exists in a release directive(s)
|
|
601
|
+
existing_release_directives = (
|
|
602
|
+
cls.get_existing_release_directive_info_for_version(
|
|
603
|
+
package_name, package_role, version
|
|
604
|
+
)
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
if existing_release_directives:
|
|
608
|
+
release_directive_names = ", ".join(
|
|
609
|
+
row["name"] for row in existing_release_directives
|
|
610
|
+
)
|
|
611
|
+
console.warning(
|
|
612
|
+
dedent(
|
|
613
|
+
f"""\
|
|
614
|
+
Version {version} already defined in application package {package_name} and in release directive(s): {release_directive_names}.
|
|
615
|
+
"""
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
user_prompt = (
|
|
620
|
+
f"Are you sure you want to create a new patch for version {version} in application "
|
|
621
|
+
f"package {package_name}? Once added, this operation cannot be undone."
|
|
622
|
+
)
|
|
623
|
+
if not policy.should_proceed(user_prompt):
|
|
624
|
+
if is_interactive:
|
|
625
|
+
console.message("Not creating a new patch.")
|
|
626
|
+
raise typer.Exit(0)
|
|
627
|
+
else:
|
|
628
|
+
console.message(
|
|
629
|
+
"Cannot create a new patch non-interactively without --force."
|
|
630
|
+
)
|
|
631
|
+
raise typer.Exit(1)
|
|
632
|
+
|
|
633
|
+
# Define a new version in the application package
|
|
634
|
+
if not cls.get_existing_version_info(version, package_name, package_role):
|
|
635
|
+
cls.add_new_version(
|
|
636
|
+
console=console,
|
|
637
|
+
package_name=package_name,
|
|
638
|
+
package_role=package_role,
|
|
639
|
+
stage_fqn=stage_fqn,
|
|
640
|
+
version=version,
|
|
641
|
+
)
|
|
642
|
+
return # A new version created automatically has patch 0, we do not need to further increment the patch.
|
|
643
|
+
|
|
644
|
+
# Add a new patch to an existing (old) version
|
|
645
|
+
cls.add_new_patch_to_version(
|
|
646
|
+
console=console,
|
|
647
|
+
package_name=package_name,
|
|
648
|
+
package_role=package_role,
|
|
649
|
+
stage_fqn=stage_fqn,
|
|
650
|
+
version=version,
|
|
651
|
+
patch=patch,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
@staticmethod
|
|
655
|
+
def get_existing_version_info(
|
|
656
|
+
version: str,
|
|
657
|
+
package_name: str,
|
|
658
|
+
package_role: str,
|
|
659
|
+
) -> Optional[dict]:
|
|
660
|
+
"""
|
|
661
|
+
Get the latest patch on an existing version by name in the application package.
|
|
662
|
+
Executes 'show versions like ... in application package' query and returns
|
|
663
|
+
the latest patch in the version as a single row, if one exists. Otherwise,
|
|
664
|
+
returns None.
|
|
665
|
+
"""
|
|
666
|
+
sql_executor = get_sql_executor()
|
|
667
|
+
with sql_executor.use_role(package_role):
|
|
668
|
+
try:
|
|
669
|
+
query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
|
|
670
|
+
cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
671
|
+
|
|
672
|
+
if cursor.rowcount is None:
|
|
673
|
+
raise SnowflakeSQLExecutionError(query)
|
|
674
|
+
|
|
675
|
+
matching_rows = find_all_rows(
|
|
676
|
+
cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
if not matching_rows:
|
|
680
|
+
return None
|
|
681
|
+
|
|
682
|
+
return max(matching_rows, key=lambda row: row[PATCH_COL])
|
|
683
|
+
|
|
684
|
+
except ProgrammingError as err:
|
|
685
|
+
if err.msg.__contains__("does not exist or not authorized"):
|
|
686
|
+
raise ApplicationPackageDoesNotExistError(package_name)
|
|
687
|
+
else:
|
|
688
|
+
generic_sql_error_handler(err=err, role=package_role)
|
|
689
|
+
return None
|
|
690
|
+
|
|
691
|
+
@classmethod
|
|
692
|
+
def get_existing_release_directive_info_for_version(
|
|
693
|
+
cls,
|
|
694
|
+
package_name: str,
|
|
695
|
+
package_role: str,
|
|
696
|
+
version: str,
|
|
697
|
+
) -> List[dict]:
|
|
698
|
+
"""
|
|
699
|
+
Get all existing release directives, if present, set on the version defined in an application package.
|
|
700
|
+
It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
|
|
701
|
+
"""
|
|
702
|
+
sql_executor = get_sql_executor()
|
|
703
|
+
with sql_executor.use_role(package_role):
|
|
704
|
+
show_obj_query = (
|
|
705
|
+
f"show release directives in application package {package_name}"
|
|
706
|
+
)
|
|
707
|
+
show_obj_cursor = sql_executor.execute_query(
|
|
708
|
+
show_obj_query, cursor_class=DictCursor
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
if show_obj_cursor.rowcount is None:
|
|
712
|
+
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
713
|
+
|
|
714
|
+
show_obj_rows = find_all_rows(
|
|
715
|
+
show_obj_cursor,
|
|
716
|
+
lambda row: row[VERSION_COL] == unquote_identifier(version),
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
return show_obj_rows
|
|
720
|
+
|
|
721
|
+
@classmethod
|
|
722
|
+
def add_new_version(
|
|
723
|
+
cls,
|
|
724
|
+
console: AbstractConsole,
|
|
725
|
+
package_name: str,
|
|
726
|
+
package_role: str,
|
|
727
|
+
stage_fqn: str,
|
|
728
|
+
version: str,
|
|
729
|
+
) -> None:
|
|
730
|
+
"""
|
|
731
|
+
Defines a new version in an existing application package.
|
|
732
|
+
"""
|
|
733
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
734
|
+
version = to_identifier(version)
|
|
735
|
+
sql_executor = get_sql_executor()
|
|
736
|
+
with sql_executor.use_role(package_role):
|
|
737
|
+
console.step(
|
|
738
|
+
f"Defining a new version {version} in application package {package_name}"
|
|
739
|
+
)
|
|
740
|
+
add_version_query = dedent(
|
|
741
|
+
f"""\
|
|
742
|
+
alter application package {package_name}
|
|
743
|
+
add version {version}
|
|
744
|
+
using @{stage_fqn}
|
|
745
|
+
"""
|
|
746
|
+
)
|
|
747
|
+
sql_executor.execute_query(add_version_query, cursor_class=DictCursor)
|
|
748
|
+
console.message(
|
|
749
|
+
f"Version {version} created for application package {package_name}."
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
@classmethod
|
|
753
|
+
def add_new_patch_to_version(
|
|
754
|
+
cls,
|
|
755
|
+
console: AbstractConsole,
|
|
756
|
+
package_name: str,
|
|
757
|
+
package_role: str,
|
|
758
|
+
stage_fqn: str,
|
|
759
|
+
version: str,
|
|
760
|
+
patch: Optional[int] = None,
|
|
761
|
+
):
|
|
762
|
+
"""
|
|
763
|
+
Add a new patch, optionally a custom one, to an existing version in an application package.
|
|
764
|
+
"""
|
|
765
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
766
|
+
version = to_identifier(version)
|
|
767
|
+
sql_executor = get_sql_executor()
|
|
768
|
+
with sql_executor.use_role(package_role):
|
|
769
|
+
console.step(
|
|
770
|
+
f"Adding new patch to version {version} defined in application package {package_name}"
|
|
771
|
+
)
|
|
772
|
+
add_version_query = dedent(
|
|
773
|
+
f"""\
|
|
774
|
+
alter application package {package_name}
|
|
775
|
+
add patch {patch if patch else ""} for version {version}
|
|
776
|
+
using @{stage_fqn}
|
|
777
|
+
"""
|
|
778
|
+
)
|
|
779
|
+
result_cursor = sql_executor.execute_query(
|
|
780
|
+
add_version_query, cursor_class=DictCursor
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
show_row = result_cursor.fetchall()[0]
|
|
784
|
+
new_patch = show_row["patch"]
|
|
785
|
+
console.message(
|
|
786
|
+
f"Patch {new_patch} created for version {version} defined in application package {package_name}."
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
@classmethod
|
|
790
|
+
def check_index_changes_in_git_repo(
|
|
791
|
+
cls,
|
|
792
|
+
console: AbstractConsole,
|
|
793
|
+
project_root: Path,
|
|
794
|
+
policy: PolicyBase,
|
|
795
|
+
is_interactive: bool,
|
|
796
|
+
) -> None:
|
|
797
|
+
"""
|
|
798
|
+
Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
|
|
799
|
+
it also checks if there any local changes to the directory that may not be on the application package stage.
|
|
800
|
+
"""
|
|
801
|
+
|
|
802
|
+
from git import Repo
|
|
803
|
+
from git.exc import InvalidGitRepositoryError
|
|
804
|
+
|
|
805
|
+
try:
|
|
806
|
+
repo = Repo(project_root, search_parent_directories=True)
|
|
807
|
+
assert repo.git_dir is not None
|
|
808
|
+
|
|
809
|
+
# Check if the repo has any changes, including untracked files
|
|
810
|
+
if repo.is_dirty(untracked_files=True):
|
|
811
|
+
console.warning(
|
|
812
|
+
"Changes detected in the git repository. "
|
|
813
|
+
"(Rerun your command with --skip-git-check flag to ignore this check)"
|
|
814
|
+
)
|
|
815
|
+
repo.git.execute(["git", "status"])
|
|
816
|
+
|
|
817
|
+
user_prompt = (
|
|
818
|
+
"You have local changes in this repository that are not part of a previous commit. "
|
|
819
|
+
"Do you still want to continue?"
|
|
820
|
+
)
|
|
821
|
+
if not policy.should_proceed(user_prompt):
|
|
822
|
+
if is_interactive:
|
|
823
|
+
console.message("Not creating a new version.")
|
|
824
|
+
raise typer.Exit(0)
|
|
825
|
+
else:
|
|
826
|
+
console.message(
|
|
827
|
+
"Cannot create a new version non-interactively without --force."
|
|
828
|
+
)
|
|
829
|
+
raise typer.Exit(1)
|
|
830
|
+
|
|
831
|
+
except InvalidGitRepositoryError:
|
|
832
|
+
pass # not a git repository, which is acceptable
|
|
833
|
+
|
|
834
|
+
@classmethod
|
|
835
|
+
def version_drop(
|
|
836
|
+
cls,
|
|
837
|
+
console: AbstractConsole,
|
|
838
|
+
project_root: Path,
|
|
839
|
+
deploy_root: Path,
|
|
840
|
+
bundle_root: Path,
|
|
841
|
+
generated_root: Path,
|
|
842
|
+
artifacts: list[PathMapping],
|
|
843
|
+
package_name: str,
|
|
844
|
+
package_role: str,
|
|
845
|
+
package_distribution: str,
|
|
846
|
+
version: Optional[str],
|
|
847
|
+
force: bool,
|
|
848
|
+
interactive: bool,
|
|
849
|
+
):
|
|
850
|
+
"""
|
|
851
|
+
Drops a version defined in an application package. If --force is provided, then no user prompts will be executed.
|
|
852
|
+
"""
|
|
853
|
+
if force:
|
|
854
|
+
interactive = False
|
|
855
|
+
policy = AllowAlwaysPolicy()
|
|
856
|
+
else:
|
|
857
|
+
policy = AskAlwaysPolicy() if interactive else DenyAlwaysPolicy()
|
|
858
|
+
|
|
859
|
+
# 1. Check for existing an existing application package
|
|
860
|
+
show_obj_row = cls.get_existing_app_pkg_info(package_name, package_role)
|
|
861
|
+
if not show_obj_row:
|
|
862
|
+
raise ApplicationPackageDoesNotExistError(package_name)
|
|
863
|
+
|
|
864
|
+
# 2. Check distribution of the existing application package
|
|
865
|
+
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
866
|
+
package_name, package_role
|
|
867
|
+
)
|
|
868
|
+
if not cls.verify_project_distribution(
|
|
869
|
+
console=console,
|
|
870
|
+
package_name=package_name,
|
|
871
|
+
package_role=package_role,
|
|
872
|
+
package_distribution=package_distribution,
|
|
873
|
+
expected_distribution=actual_distribution,
|
|
874
|
+
):
|
|
875
|
+
console.warning(
|
|
876
|
+
f"Continuing to execute version drop on application package "
|
|
877
|
+
f"{package_name} with distribution '{actual_distribution}'."
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
# 3. If the user did not pass in a version string, determine from manifest.yml
|
|
881
|
+
if not version:
|
|
882
|
+
console.message(
|
|
883
|
+
dedent(
|
|
884
|
+
f"""\
|
|
885
|
+
Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
|
|
886
|
+
This step will bundle your app artifacts to determine the location of the manifest.yml file.
|
|
887
|
+
"""
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
cls.bundle(
|
|
891
|
+
project_root=project_root,
|
|
892
|
+
deploy_root=deploy_root,
|
|
893
|
+
bundle_root=bundle_root,
|
|
894
|
+
generated_root=generated_root,
|
|
895
|
+
artifacts=artifacts,
|
|
896
|
+
package_name=package_name,
|
|
897
|
+
)
|
|
898
|
+
version, _ = find_version_info_in_manifest_file(deploy_root)
|
|
899
|
+
if not version:
|
|
900
|
+
raise ClickException(
|
|
901
|
+
"Manifest.yml file does not contain a value for the version field."
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
905
|
+
version = to_identifier(version)
|
|
906
|
+
|
|
907
|
+
console.step(
|
|
908
|
+
f"About to drop version {version} in application package {package_name}."
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
# If user did not provide --force, ask for confirmation
|
|
912
|
+
user_prompt = (
|
|
913
|
+
f"Are you sure you want to drop version {version} "
|
|
914
|
+
f"in application package {package_name}? "
|
|
915
|
+
f"Once dropped, this operation cannot be undone."
|
|
916
|
+
)
|
|
917
|
+
if not policy.should_proceed(user_prompt):
|
|
918
|
+
if interactive:
|
|
919
|
+
console.message("Not dropping version.")
|
|
920
|
+
raise typer.Exit(0)
|
|
921
|
+
else:
|
|
922
|
+
console.message(
|
|
923
|
+
"Cannot drop version non-interactively without --force."
|
|
924
|
+
)
|
|
925
|
+
raise typer.Exit(1)
|
|
926
|
+
|
|
927
|
+
# Drop the version
|
|
928
|
+
sql_executor = get_sql_executor()
|
|
929
|
+
with sql_executor.use_role(package_role):
|
|
930
|
+
try:
|
|
931
|
+
sql_executor.execute_query(
|
|
932
|
+
f"alter application package {package_name} drop version {version}"
|
|
933
|
+
)
|
|
934
|
+
except ProgrammingError as err:
|
|
935
|
+
raise err # e.g. version is referenced in a release directive(s)
|
|
936
|
+
|
|
937
|
+
console.message(
|
|
938
|
+
f"Version {version} in application package {package_name} dropped successfully."
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
@staticmethod
|
|
942
|
+
def get_existing_app_pkg_info(
|
|
943
|
+
package_name: str,
|
|
944
|
+
package_role: str,
|
|
945
|
+
) -> Optional[dict]:
|
|
946
|
+
"""
|
|
947
|
+
Check for an existing application package by the same name as in project definition, in account.
|
|
948
|
+
It executes a 'show application packages like' query and returns the result as single row, if one exists.
|
|
949
|
+
"""
|
|
950
|
+
sql_executor = get_sql_executor()
|
|
951
|
+
with sql_executor.use_role(package_role):
|
|
952
|
+
return sql_executor.show_specific_object(
|
|
953
|
+
"application packages", package_name, name_col=NAME_COL
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
@staticmethod
|
|
957
|
+
def get_app_pkg_distribution_in_snowflake(
|
|
958
|
+
package_name: str,
|
|
959
|
+
package_role: str,
|
|
960
|
+
) -> str:
|
|
961
|
+
"""
|
|
962
|
+
Returns the 'distribution' attribute of a 'describe application package' SQL query, in lowercase.
|
|
963
|
+
"""
|
|
964
|
+
sql_executor = get_sql_executor()
|
|
965
|
+
with sql_executor.use_role(package_role):
|
|
966
|
+
try:
|
|
967
|
+
desc_cursor = sql_executor.execute_query(
|
|
968
|
+
f"describe application package {package_name}"
|
|
969
|
+
)
|
|
970
|
+
except ProgrammingError as err:
|
|
971
|
+
generic_sql_error_handler(err)
|
|
972
|
+
|
|
973
|
+
if desc_cursor.rowcount is None or desc_cursor.rowcount == 0:
|
|
974
|
+
raise SnowflakeSQLExecutionError()
|
|
975
|
+
else:
|
|
976
|
+
for row in desc_cursor:
|
|
977
|
+
if row[0].lower() == "distribution":
|
|
978
|
+
return row[1].lower()
|
|
979
|
+
raise ProgrammingError(
|
|
980
|
+
msg=dedent(
|
|
981
|
+
f"""\
|
|
982
|
+
Could not find the 'distribution' attribute for application package {package_name} in the output of SQL query:
|
|
983
|
+
'describe application package {package_name}'
|
|
984
|
+
"""
|
|
985
|
+
)
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
@classmethod
|
|
989
|
+
def verify_project_distribution(
|
|
990
|
+
cls,
|
|
991
|
+
console: AbstractConsole,
|
|
992
|
+
package_name: str,
|
|
993
|
+
package_role: str,
|
|
994
|
+
package_distribution: str,
|
|
995
|
+
expected_distribution: Optional[str] = None,
|
|
996
|
+
) -> bool:
|
|
997
|
+
"""
|
|
998
|
+
Returns true if the 'distribution' attribute of an existing application package in snowflake
|
|
999
|
+
is the same as the the attribute specified in project definition file.
|
|
1000
|
+
"""
|
|
1001
|
+
actual_distribution = (
|
|
1002
|
+
expected_distribution
|
|
1003
|
+
if expected_distribution
|
|
1004
|
+
else cls.get_app_pkg_distribution_in_snowflake(
|
|
1005
|
+
package_name=package_name,
|
|
1006
|
+
package_role=package_role,
|
|
1007
|
+
)
|
|
1008
|
+
)
|
|
1009
|
+
project_def_distribution = package_distribution.lower()
|
|
1010
|
+
if actual_distribution != project_def_distribution:
|
|
1011
|
+
console.warning(
|
|
1012
|
+
dedent(
|
|
1013
|
+
f"""\
|
|
1014
|
+
Application package {package_name} in your Snowflake account has distribution property {actual_distribution},
|
|
1015
|
+
which does not match the value specified in project definition file: {project_def_distribution}.
|
|
1016
|
+
"""
|
|
1017
|
+
)
|
|
1018
|
+
)
|
|
1019
|
+
return False
|
|
1020
|
+
return True
|
|
1021
|
+
|
|
1022
|
+
@staticmethod
|
|
1023
|
+
@contextmanager
|
|
1024
|
+
def use_package_warehouse(
|
|
1025
|
+
package_warehouse: Optional[str],
|
|
1026
|
+
):
|
|
1027
|
+
if package_warehouse:
|
|
1028
|
+
with get_sql_executor().use_warehouse(package_warehouse):
|
|
1029
|
+
yield
|
|
1030
|
+
else:
|
|
1031
|
+
raise ClickException(
|
|
1032
|
+
dedent(
|
|
1033
|
+
f"""\
|
|
1034
|
+
Application package warehouse cannot be empty.
|
|
1035
|
+
Please provide a value for it in your connection information or your project definition file.
|
|
1036
|
+
"""
|
|
1037
|
+
)
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
@classmethod
|
|
1041
|
+
def apply_package_scripts(
|
|
1042
|
+
cls,
|
|
1043
|
+
console: AbstractConsole,
|
|
1044
|
+
package_scripts: List[str],
|
|
1045
|
+
package_warehouse: Optional[str],
|
|
1046
|
+
project_root: Path,
|
|
1047
|
+
package_role: str,
|
|
1048
|
+
package_name: str,
|
|
1049
|
+
) -> None:
|
|
1050
|
+
"""
|
|
1051
|
+
Assuming the application package exists and we are using the correct role,
|
|
1052
|
+
applies all package scripts in-order to the application package.
|
|
1053
|
+
"""
|
|
1054
|
+
|
|
1055
|
+
metrics = get_cli_context().metrics
|
|
1056
|
+
metrics.set_counter_default(CLICounterField.PACKAGE_SCRIPTS, 0)
|
|
1057
|
+
|
|
1058
|
+
if not package_scripts:
|
|
1059
|
+
return
|
|
1060
|
+
|
|
1061
|
+
metrics.set_counter(CLICounterField.PACKAGE_SCRIPTS, 1)
|
|
1062
|
+
|
|
1063
|
+
console.warning(
|
|
1064
|
+
"WARNING: native_app.package.scripts is deprecated. Please migrate to using native_app.package.post_deploy."
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
queued_queries = render_script_templates(
|
|
1068
|
+
project_root,
|
|
1069
|
+
dict(package_name=package_name),
|
|
1070
|
+
package_scripts,
|
|
1071
|
+
get_basic_jinja_env(),
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
# once we're sure all the templates expanded correctly, execute all of them
|
|
1075
|
+
with cls.use_package_warehouse(
|
|
1076
|
+
package_warehouse=package_warehouse,
|
|
1077
|
+
):
|
|
1078
|
+
try:
|
|
1079
|
+
for i, queries in enumerate(queued_queries):
|
|
1080
|
+
console.step(f"Applying package script: {package_scripts[i]}")
|
|
1081
|
+
get_sql_executor().execute_queries(queries)
|
|
1082
|
+
except ProgrammingError as err:
|
|
1083
|
+
generic_sql_error_handler(
|
|
1084
|
+
err, role=package_role, warehouse=package_warehouse
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
@classmethod
|
|
1088
|
+
def create_app_package(
|
|
1089
|
+
cls,
|
|
1090
|
+
console: AbstractConsole,
|
|
1091
|
+
package_name: str,
|
|
1092
|
+
package_role: str,
|
|
1093
|
+
package_distribution: str,
|
|
1094
|
+
) -> None:
|
|
1095
|
+
"""
|
|
1096
|
+
Creates the application package with our up-to-date stage if none exists.
|
|
1097
|
+
"""
|
|
1098
|
+
|
|
1099
|
+
# 1. Check for existing existing application package
|
|
1100
|
+
show_obj_row = cls.get_existing_app_pkg_info(
|
|
1101
|
+
package_name=package_name,
|
|
1102
|
+
package_role=package_role,
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
if show_obj_row:
|
|
1106
|
+
# 2. Check distribution of the existing application package
|
|
1107
|
+
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
1108
|
+
package_name=package_name,
|
|
1109
|
+
package_role=package_role,
|
|
1110
|
+
)
|
|
1111
|
+
if not cls.verify_project_distribution(
|
|
1112
|
+
console=console,
|
|
1113
|
+
package_name=package_name,
|
|
1114
|
+
package_role=package_role,
|
|
1115
|
+
package_distribution=package_distribution,
|
|
1116
|
+
expected_distribution=actual_distribution,
|
|
1117
|
+
):
|
|
1118
|
+
console.warning(
|
|
1119
|
+
f"Continuing to execute `snow app run` on application package {package_name} with distribution '{actual_distribution}'."
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
# 3. If actual_distribution is external, skip comment check
|
|
1123
|
+
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
1124
|
+
row_comment = show_obj_row[COMMENT_COL]
|
|
1125
|
+
|
|
1126
|
+
if row_comment not in ALLOWED_SPECIAL_COMMENTS:
|
|
1127
|
+
raise ApplicationPackageAlreadyExistsError(package_name)
|
|
1128
|
+
|
|
1129
|
+
return
|
|
1130
|
+
|
|
1131
|
+
# If no application package pre-exists, create an application package, with the specified distribution in the project definition file.
|
|
1132
|
+
sql_executor = get_sql_executor()
|
|
1133
|
+
with sql_executor.use_role(package_role):
|
|
1134
|
+
console.step(f"Creating new application package {package_name} in account.")
|
|
1135
|
+
sql_executor.execute_query(
|
|
1136
|
+
dedent(
|
|
1137
|
+
f"""\
|
|
1138
|
+
create application package {package_name}
|
|
1139
|
+
comment = {SPECIAL_COMMENT}
|
|
1140
|
+
distribution = {package_distribution}
|
|
1141
|
+
"""
|
|
1142
|
+
)
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
@classmethod
|
|
1146
|
+
def execute_post_deploy_hooks(
|
|
1147
|
+
cls,
|
|
1148
|
+
console: AbstractConsole,
|
|
1149
|
+
project_root: Path,
|
|
1150
|
+
post_deploy_hooks: Optional[List[PostDeployHook]],
|
|
1151
|
+
package_name: str,
|
|
1152
|
+
package_warehouse: Optional[str],
|
|
1153
|
+
):
|
|
1154
|
+
get_cli_context().metrics.set_counter_default(
|
|
1155
|
+
CLICounterField.POST_DEPLOY_SCRIPTS, 0
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
if post_deploy_hooks:
|
|
1159
|
+
with cls.use_package_warehouse(package_warehouse):
|
|
1160
|
+
execute_post_deploy_hooks(
|
|
1161
|
+
console=console,
|
|
1162
|
+
project_root=project_root,
|
|
1163
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
1164
|
+
deployed_object_type="application package",
|
|
1165
|
+
database_name=package_name,
|
|
1166
|
+
)
|
|
1167
|
+
|
|
1168
|
+
@classmethod
|
|
1169
|
+
def validate_setup_script(
|
|
1170
|
+
cls,
|
|
1171
|
+
console: AbstractConsole,
|
|
1172
|
+
project_root: Path,
|
|
1173
|
+
deploy_root: Path,
|
|
1174
|
+
bundle_root: Path,
|
|
1175
|
+
generated_root: Path,
|
|
1176
|
+
artifacts: list[PathMapping],
|
|
1177
|
+
package_name: str,
|
|
1178
|
+
package_role: str,
|
|
1179
|
+
package_distribution: str,
|
|
1180
|
+
package_warehouse: str | None,
|
|
1181
|
+
prune: bool,
|
|
1182
|
+
recursive: bool,
|
|
1183
|
+
paths: List[Path] | None,
|
|
1184
|
+
stage_fqn: str,
|
|
1185
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
1186
|
+
package_scripts: List[str],
|
|
1187
|
+
policy: PolicyBase,
|
|
1188
|
+
use_scratch_stage: bool,
|
|
1189
|
+
scratch_stage_fqn: str,
|
|
1190
|
+
):
|
|
1191
|
+
"""Validates Native App setup script SQL."""
|
|
1192
|
+
with console.phase(f"Validating Snowflake Native App setup script."):
|
|
1193
|
+
validation_result = cls.get_validation_result(
|
|
1194
|
+
console=console,
|
|
1195
|
+
project_root=project_root,
|
|
1196
|
+
deploy_root=deploy_root,
|
|
1197
|
+
bundle_root=bundle_root,
|
|
1198
|
+
generated_root=generated_root,
|
|
1199
|
+
artifacts=artifacts,
|
|
1200
|
+
package_name=package_name,
|
|
1201
|
+
package_role=package_role,
|
|
1202
|
+
package_distribution=package_distribution,
|
|
1203
|
+
prune=prune,
|
|
1204
|
+
recursive=recursive,
|
|
1205
|
+
paths=paths,
|
|
1206
|
+
stage_fqn=stage_fqn,
|
|
1207
|
+
package_warehouse=package_warehouse,
|
|
1208
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
1209
|
+
package_scripts=package_scripts,
|
|
1210
|
+
policy=policy,
|
|
1211
|
+
use_scratch_stage=use_scratch_stage,
|
|
1212
|
+
scratch_stage_fqn=scratch_stage_fqn,
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
# First print warnings, regardless of the outcome of validation
|
|
1216
|
+
for warning in validation_result.get("warnings", []):
|
|
1217
|
+
console.warning(validation_item_to_str(warning))
|
|
1218
|
+
|
|
1219
|
+
# Then print errors
|
|
1220
|
+
for error in validation_result.get("errors", []):
|
|
1221
|
+
# Print them as warnings for now since we're going to be
|
|
1222
|
+
# revamping CLI output soon
|
|
1223
|
+
console.warning(validation_item_to_str(error))
|
|
1224
|
+
|
|
1225
|
+
# Then raise an exception if validation failed
|
|
1226
|
+
if validation_result["status"] == "FAIL":
|
|
1227
|
+
raise SetupScriptFailedValidation()
|
|
1228
|
+
|
|
1229
|
+
@classmethod
|
|
1230
|
+
def get_validation_result(
|
|
1231
|
+
cls,
|
|
1232
|
+
console: AbstractConsole,
|
|
1233
|
+
project_root: Path,
|
|
1234
|
+
deploy_root: Path,
|
|
1235
|
+
bundle_root: Path,
|
|
1236
|
+
generated_root: Path,
|
|
1237
|
+
artifacts: list[PathMapping],
|
|
1238
|
+
package_name: str,
|
|
1239
|
+
package_role: str,
|
|
1240
|
+
package_distribution: str,
|
|
1241
|
+
package_warehouse: str | None,
|
|
1242
|
+
prune: bool,
|
|
1243
|
+
recursive: bool,
|
|
1244
|
+
paths: List[Path] | None,
|
|
1245
|
+
stage_fqn: str,
|
|
1246
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
1247
|
+
package_scripts: List[str],
|
|
1248
|
+
policy: PolicyBase,
|
|
1249
|
+
use_scratch_stage: bool,
|
|
1250
|
+
scratch_stage_fqn: str,
|
|
1251
|
+
):
|
|
1252
|
+
"""Call system$validate_native_app_setup() to validate deployed Native App setup script."""
|
|
1253
|
+
if use_scratch_stage:
|
|
1254
|
+
stage_fqn = scratch_stage_fqn
|
|
1255
|
+
cls.deploy(
|
|
1256
|
+
console=console,
|
|
1257
|
+
project_root=project_root,
|
|
1258
|
+
deploy_root=deploy_root,
|
|
1259
|
+
bundle_root=bundle_root,
|
|
1260
|
+
generated_root=generated_root,
|
|
1261
|
+
artifacts=artifacts,
|
|
1262
|
+
bundle_map=None,
|
|
1263
|
+
package_name=package_name,
|
|
1264
|
+
package_role=package_role,
|
|
1265
|
+
package_distribution=package_distribution,
|
|
1266
|
+
prune=prune,
|
|
1267
|
+
recursive=recursive,
|
|
1268
|
+
paths=paths,
|
|
1269
|
+
print_diff=False,
|
|
1270
|
+
validate=False,
|
|
1271
|
+
stage_fqn=stage_fqn,
|
|
1272
|
+
package_warehouse=package_warehouse,
|
|
1273
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
1274
|
+
package_scripts=package_scripts,
|
|
1275
|
+
policy=policy,
|
|
1276
|
+
)
|
|
1277
|
+
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
|
|
1278
|
+
sql_executor = get_sql_executor()
|
|
1279
|
+
try:
|
|
1280
|
+
cursor = sql_executor.execute_query(
|
|
1281
|
+
f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
|
|
1282
|
+
)
|
|
1283
|
+
except ProgrammingError as err:
|
|
1284
|
+
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
1285
|
+
raise ApplicationPackageDoesNotExistError(package_name)
|
|
1286
|
+
generic_sql_error_handler(err)
|
|
1287
|
+
else:
|
|
1288
|
+
if not cursor.rowcount:
|
|
1289
|
+
raise SnowflakeSQLExecutionError()
|
|
1290
|
+
return json.loads(cursor.fetchone()[0])
|
|
1291
|
+
finally:
|
|
1292
|
+
if use_scratch_stage:
|
|
1293
|
+
console.step(f"Dropping stage {scratch_stage_fqn}.")
|
|
1294
|
+
with sql_executor.use_role(package_role):
|
|
1295
|
+
sql_executor.execute_query(
|
|
1296
|
+
f"drop stage if exists {scratch_stage_fqn}"
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
@classmethod
|
|
1300
|
+
def drop(
|
|
1301
|
+
cls,
|
|
1302
|
+
console: AbstractConsole,
|
|
1303
|
+
package_name: str,
|
|
1304
|
+
package_role: str,
|
|
1305
|
+
force_drop: bool,
|
|
1306
|
+
):
|
|
1307
|
+
sql_executor = get_sql_executor()
|
|
1308
|
+
needs_confirm = True
|
|
1309
|
+
|
|
1310
|
+
# 1. If existing application package is not found, exit gracefully
|
|
1311
|
+
show_obj_row = cls.get_existing_app_pkg_info(
|
|
1312
|
+
package_name=package_name,
|
|
1313
|
+
package_role=package_role,
|
|
1314
|
+
)
|
|
1315
|
+
if show_obj_row is None:
|
|
1316
|
+
console.warning(
|
|
1317
|
+
f"Role {package_role} does not own any application package with the name {package_name}, or the application package does not exist."
|
|
1318
|
+
)
|
|
1319
|
+
return
|
|
1320
|
+
|
|
1321
|
+
with sql_executor.use_role(package_role):
|
|
1322
|
+
# 2. Check for versions in the application package
|
|
1323
|
+
show_versions_query = f"show versions in application package {package_name}"
|
|
1324
|
+
show_versions_cursor = sql_executor.execute_query(
|
|
1325
|
+
show_versions_query, cursor_class=DictCursor
|
|
1326
|
+
)
|
|
1327
|
+
if show_versions_cursor.rowcount is None:
|
|
1328
|
+
raise SnowflakeSQLExecutionError(show_versions_query)
|
|
1329
|
+
|
|
1330
|
+
if show_versions_cursor.rowcount > 0:
|
|
1331
|
+
# allow dropping a package with versions when --force is set
|
|
1332
|
+
if not force_drop:
|
|
1333
|
+
raise CouldNotDropApplicationPackageWithVersions(
|
|
1334
|
+
"Drop versions first, or use --force to override."
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
# 3. Check distribution of the existing application package
|
|
1338
|
+
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
1339
|
+
package_name=package_name,
|
|
1340
|
+
package_role=package_role,
|
|
1341
|
+
)
|
|
1342
|
+
if not cls.verify_project_distribution(
|
|
1343
|
+
console=console,
|
|
1344
|
+
package_name=package_name,
|
|
1345
|
+
package_role=package_role,
|
|
1346
|
+
package_distribution=actual_distribution,
|
|
1347
|
+
):
|
|
1348
|
+
console.warning(
|
|
1349
|
+
f"Dropping application package {package_name} with distribution '{actual_distribution}'."
|
|
1350
|
+
)
|
|
1351
|
+
|
|
1352
|
+
# 4. If distribution is internal, check if created by the Snowflake CLI
|
|
1353
|
+
row_comment = show_obj_row[COMMENT_COL]
|
|
1354
|
+
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
1355
|
+
if row_comment in ALLOWED_SPECIAL_COMMENTS:
|
|
1356
|
+
needs_confirm = False
|
|
1357
|
+
else:
|
|
1358
|
+
if needs_confirmation(needs_confirm, force_drop):
|
|
1359
|
+
console.warning(
|
|
1360
|
+
f"Application package {package_name} was not created by Snowflake CLI."
|
|
1361
|
+
)
|
|
1362
|
+
else:
|
|
1363
|
+
if needs_confirmation(needs_confirm, force_drop):
|
|
1364
|
+
console.warning(
|
|
1365
|
+
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."
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
if needs_confirmation(needs_confirm, force_drop):
|
|
1369
|
+
should_drop_object = typer.confirm(
|
|
1370
|
+
dedent(
|
|
1371
|
+
f"""\
|
|
1372
|
+
Application package details:
|
|
1373
|
+
Name: {package_name}
|
|
1374
|
+
Created on: {show_obj_row["created_on"]}
|
|
1375
|
+
Distribution: {actual_distribution}
|
|
1376
|
+
Owner: {show_obj_row[OWNER_COL]}
|
|
1377
|
+
Comment: {show_obj_row[COMMENT_COL]}
|
|
1378
|
+
Are you sure you want to drop it?
|
|
1379
|
+
"""
|
|
1380
|
+
)
|
|
1381
|
+
)
|
|
1382
|
+
if not should_drop_object:
|
|
1383
|
+
console.message(f"Did not drop application package {package_name}.")
|
|
1384
|
+
return # The user desires to keep the application package, therefore exit gracefully
|
|
1385
|
+
|
|
1386
|
+
# All validations have passed, drop object
|
|
1387
|
+
drop_generic_object(
|
|
1388
|
+
console=console,
|
|
1389
|
+
object_type="application package",
|
|
1390
|
+
object_name=package_name,
|
|
1391
|
+
role=package_role,
|
|
1392
|
+
)
|