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