snowflake-cli 3.0.2__py3-none-any.whl → 3.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +3 -0
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
- snowflake/cli/_app/telemetry.py +69 -4
- snowflake/cli/_plugins/connection/commands.py +152 -99
- snowflake/cli/_plugins/connection/util.py +54 -9
- snowflake/cli/_plugins/cortex/manager.py +1 -1
- snowflake/cli/_plugins/git/commands.py +6 -3
- snowflake/cli/_plugins/git/manager.py +9 -4
- snowflake/cli/_plugins/nativeapp/artifacts.py +77 -13
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +5 -3
- snowflake/cli/_plugins/nativeapp/commands.py +144 -188
- snowflake/cli/_plugins/nativeapp/constants.py +1 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +564 -351
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +583 -929
- snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py +58 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +12 -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/{v2_to_v1_decorator.py → compat.py} +88 -117
- snowflake/cli/_plugins/nativeapp/version/commands.py +36 -32
- 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 +63 -21
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -3
- 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/commands.py +4 -37
- snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
- snowflake/cli/_plugins/spcs/services/commands.py +100 -17
- snowflake/cli/_plugins/spcs/services/manager.py +108 -16
- snowflake/cli/_plugins/sql/commands.py +9 -1
- snowflake/cli/_plugins/sql/manager.py +9 -4
- snowflake/cli/_plugins/stage/commands.py +28 -19
- snowflake/cli/_plugins/stage/diff.py +17 -17
- snowflake/cli/_plugins/stage/manager.py +304 -84
- snowflake/cli/_plugins/stage/md5.py +1 -1
- snowflake/cli/_plugins/streamlit/manager.py +5 -5
- snowflake/cli/_plugins/workspace/commands.py +27 -4
- snowflake/cli/_plugins/workspace/context.py +38 -0
- snowflake/cli/_plugins/workspace/manager.py +23 -13
- snowflake/cli/api/cli_global_context.py +4 -3
- snowflake/cli/api/commands/flags.py +23 -7
- snowflake/cli/api/config.py +30 -9
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/console/console.py +4 -19
- snowflake/cli/api/entities/common.py +4 -2
- snowflake/cli/api/entities/utils.py +36 -69
- snowflake/cli/api/errno.py +2 -0
- snowflake/cli/api/exceptions.py +41 -0
- snowflake/cli/api/identifiers.py +8 -0
- snowflake/cli/api/metrics.py +223 -7
- snowflake/cli/api/output/types.py +1 -1
- snowflake/cli/api/project/definition_conversion.py +293 -77
- snowflake/cli/api/project/schemas/entities/common.py +11 -0
- snowflake/cli/api/project/schemas/project_definition.py +30 -25
- snowflake/cli/api/rest_api.py +26 -4
- snowflake/cli/api/secure_utils.py +1 -1
- snowflake/cli/api/sql_execution.py +40 -29
- snowflake/cli/api/stage_path.py +244 -0
- snowflake/cli/api/utils/definition_rendering.py +3 -5
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/METADATA +14 -15
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/RECORD +78 -77
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/WHEEL +1 -1
- snowflake/cli/_plugins/nativeapp/manager.py +0 -415
- snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
- snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.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
|
)
|
|
@@ -31,6 +32,7 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
|
31
32
|
ApplicationPackageAlreadyExistsError,
|
|
32
33
|
ApplicationPackageDoesNotExistError,
|
|
33
34
|
CouldNotDropApplicationPackageWithVersions,
|
|
35
|
+
ObjectPropertyNotFoundError,
|
|
34
36
|
SetupScriptFailedValidation,
|
|
35
37
|
)
|
|
36
38
|
from snowflake.cli._plugins.nativeapp.policy import (
|
|
@@ -39,24 +41,24 @@ from snowflake.cli._plugins.nativeapp.policy import (
|
|
|
39
41
|
DenyAlwaysPolicy,
|
|
40
42
|
PolicyBase,
|
|
41
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
|
+
)
|
|
42
48
|
from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
|
|
43
49
|
from snowflake.cli._plugins.stage.diff import DiffResult
|
|
44
50
|
from snowflake.cli._plugins.stage.manager import StageManager
|
|
45
|
-
from snowflake.cli._plugins.workspace.
|
|
46
|
-
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
47
|
-
from snowflake.cli.api.console.abc import AbstractConsole
|
|
51
|
+
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
48
52
|
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
49
53
|
from snowflake.cli.api.entities.utils import (
|
|
50
54
|
drop_generic_object,
|
|
51
55
|
execute_post_deploy_hooks,
|
|
52
56
|
generic_sql_error_handler,
|
|
53
|
-
render_script_templates,
|
|
54
57
|
sync_deploy_root_with_stage,
|
|
55
58
|
validation_item_to_str,
|
|
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,13 +71,13 @@ 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,
|
|
75
78
|
to_identifier,
|
|
76
79
|
unquote_identifier,
|
|
77
80
|
)
|
|
78
|
-
from snowflake.cli.api.rendering.jinja import get_basic_jinja_env
|
|
79
81
|
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
80
82
|
from snowflake.connector import DictCursor, ProgrammingError
|
|
81
83
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -110,8 +112,9 @@ class ApplicationPackageEntityModel(EntityModelBase):
|
|
|
110
112
|
title="Distribution of the application package created by the Snowflake CLI",
|
|
111
113
|
default="internal",
|
|
112
114
|
)
|
|
113
|
-
manifest: str = Field(
|
|
114
|
-
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="",
|
|
115
118
|
)
|
|
116
119
|
|
|
117
120
|
@field_validator("identifier")
|
|
@@ -144,26 +147,72 @@ class ApplicationPackageEntityModel(EntityModelBase):
|
|
|
144
147
|
|
|
145
148
|
return transformed_artifacts
|
|
146
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
|
+
|
|
147
159
|
|
|
148
160
|
class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
149
161
|
"""
|
|
150
162
|
A Native App application package.
|
|
151
163
|
"""
|
|
152
164
|
|
|
153
|
-
|
|
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:
|
|
154
187
|
model = self._entity_model
|
|
155
|
-
return self.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
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()
|
|
163
212
|
|
|
164
213
|
def action_deploy(
|
|
165
214
|
self,
|
|
166
|
-
|
|
215
|
+
action_ctx: ActionContext,
|
|
167
216
|
prune: bool,
|
|
168
217
|
recursive: bool,
|
|
169
218
|
paths: List[Path],
|
|
@@ -174,256 +223,381 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
174
223
|
*args,
|
|
175
224
|
**kwargs,
|
|
176
225
|
):
|
|
177
|
-
|
|
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,
|
|
226
|
+
return self._deploy(
|
|
194
227
|
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
228
|
prune=prune,
|
|
199
229
|
recursive=recursive,
|
|
200
230
|
paths=paths,
|
|
201
231
|
print_diff=True,
|
|
202
232
|
validate=validate,
|
|
203
|
-
stage_fqn=stage_fqn or
|
|
204
|
-
|
|
205
|
-
|
|
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,
|
|
233
|
+
stage_fqn=stage_fqn or self.stage_fqn,
|
|
234
|
+
interactive=interactive,
|
|
235
|
+
force=force,
|
|
210
236
|
)
|
|
211
237
|
|
|
212
|
-
def action_drop(self,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
238
|
+
def action_drop(self, action_ctx: ActionContext, force_drop: bool, *args, **kwargs):
|
|
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
|
+
)
|
|
217
284
|
else:
|
|
218
|
-
|
|
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
|
+
)
|
|
219
289
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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),
|
|
225
314
|
)
|
|
226
315
|
|
|
227
316
|
def action_validate(
|
|
228
|
-
self,
|
|
317
|
+
self,
|
|
318
|
+
action_ctx: ActionContext,
|
|
319
|
+
interactive: bool,
|
|
320
|
+
force: bool,
|
|
321
|
+
use_scratch_stage: bool = True,
|
|
322
|
+
*args,
|
|
323
|
+
**kwargs,
|
|
229
324
|
):
|
|
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
325
|
self.validate_setup_script(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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}",
|
|
326
|
+
use_scratch_stage=use_scratch_stage,
|
|
327
|
+
interactive=interactive,
|
|
328
|
+
force=force,
|
|
261
329
|
)
|
|
262
|
-
|
|
330
|
+
self._workspace_ctx.console.message("Setup script is valid")
|
|
263
331
|
|
|
264
332
|
def action_version_list(
|
|
265
|
-
self,
|
|
333
|
+
self, action_ctx: ActionContext, *args, **kwargs
|
|
266
334
|
) -> SnowflakeCursor:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
)
|
|
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
|
|
272
348
|
|
|
273
349
|
def action_version_create(
|
|
274
350
|
self,
|
|
275
|
-
|
|
351
|
+
action_ctx: ActionContext,
|
|
276
352
|
version: Optional[str],
|
|
277
353
|
patch: Optional[int],
|
|
354
|
+
label: Optional[str],
|
|
278
355
|
skip_git_check: bool,
|
|
279
356
|
interactive: bool,
|
|
280
357
|
force: bool,
|
|
281
358
|
*args,
|
|
282
359
|
**kwargs,
|
|
283
360
|
):
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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,
|
|
296
395
|
prune=True,
|
|
297
396
|
recursive=True,
|
|
298
|
-
paths=
|
|
397
|
+
paths=[],
|
|
299
398
|
print_diff=True,
|
|
300
399
|
validate=True,
|
|
301
|
-
stage_fqn=
|
|
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,
|
|
400
|
+
stage_fqn=self.stage_fqn,
|
|
311
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
|
|
312
448
|
)
|
|
313
449
|
|
|
314
450
|
def action_version_drop(
|
|
315
451
|
self,
|
|
316
|
-
|
|
452
|
+
action_ctx: ActionContext,
|
|
317
453
|
version: Optional[str],
|
|
318
454
|
interactive: bool,
|
|
319
455
|
force: bool,
|
|
320
456
|
*args,
|
|
321
457
|
**kwargs,
|
|
322
458
|
):
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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}."
|
|
338
508
|
)
|
|
339
509
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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)
|
|
350
543
|
bundle_context = BundleContext(
|
|
351
|
-
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,
|
|
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,
|
|
357
550
|
)
|
|
358
551
|
compiler = NativeAppCompiler(bundle_context)
|
|
359
552
|
compiler.compile_artifacts()
|
|
360
553
|
return bundle_map
|
|
361
554
|
|
|
362
|
-
|
|
363
|
-
|
|
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],
|
|
555
|
+
def _deploy(
|
|
556
|
+
self,
|
|
371
557
|
bundle_map: BundleMap | None,
|
|
372
|
-
package_name: str,
|
|
373
|
-
package_role: str,
|
|
374
|
-
package_distribution: str,
|
|
375
|
-
package_warehouse: str | None,
|
|
376
558
|
prune: bool,
|
|
377
559
|
recursive: bool,
|
|
378
|
-
paths:
|
|
560
|
+
paths: list[Path],
|
|
379
561
|
print_diff: bool,
|
|
380
562
|
validate: bool,
|
|
381
563
|
stage_fqn: str,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
564
|
+
interactive: bool,
|
|
565
|
+
force: bool,
|
|
566
|
+
run_post_deploy_hooks: bool = True,
|
|
385
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
|
+
|
|
386
580
|
# 1. Create a bundle if one wasn't passed in
|
|
387
|
-
bundle_map = bundle_map or
|
|
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
|
-
)
|
|
581
|
+
bundle_map = bundle_map or self._bundle()
|
|
395
582
|
|
|
396
583
|
# 2. Create an empty application package, if none exists
|
|
397
584
|
try:
|
|
398
|
-
|
|
399
|
-
console=console,
|
|
400
|
-
package_name=package_name,
|
|
401
|
-
package_role=package_role,
|
|
402
|
-
package_distribution=package_distribution,
|
|
403
|
-
)
|
|
585
|
+
self.create_app_package()
|
|
404
586
|
except ApplicationPackageAlreadyExistsError as e:
|
|
405
587
|
console.warning(e.message)
|
|
406
588
|
if not policy.should_proceed("Proceed with using this package?"):
|
|
407
589
|
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
590
|
|
|
591
|
+
with get_sql_executor().use_role(self.role):
|
|
418
592
|
# 3. Upload files from deploy root local folder to the above stage
|
|
419
593
|
stage_schema = extract_schema(stage_fqn)
|
|
420
594
|
diff = sync_deploy_root_with_stage(
|
|
421
595
|
console=console,
|
|
422
|
-
deploy_root=deploy_root,
|
|
423
|
-
package_name=
|
|
596
|
+
deploy_root=self.deploy_root,
|
|
597
|
+
package_name=self.name,
|
|
424
598
|
stage_schema=stage_schema,
|
|
425
599
|
bundle_map=bundle_map,
|
|
426
|
-
role=
|
|
600
|
+
role=self.role,
|
|
427
601
|
prune=prune,
|
|
428
602
|
recursive=recursive,
|
|
429
603
|
stage_fqn=stage_fqn,
|
|
@@ -431,232 +605,19 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
431
605
|
print_diff=print_diff,
|
|
432
606
|
)
|
|
433
607
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
project_root=project_root,
|
|
437
|
-
post_deploy_hooks=post_deploy_hooks,
|
|
438
|
-
package_name=package_name,
|
|
439
|
-
package_warehouse=package_warehouse,
|
|
440
|
-
)
|
|
608
|
+
if run_post_deploy_hooks:
|
|
609
|
+
self.execute_post_deploy_hooks()
|
|
441
610
|
|
|
442
611
|
if validate:
|
|
443
|
-
|
|
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,
|
|
612
|
+
self.validate_setup_script(
|
|
461
613
|
use_scratch_stage=False,
|
|
462
|
-
|
|
614
|
+
interactive=interactive,
|
|
615
|
+
force=force,
|
|
463
616
|
)
|
|
464
617
|
|
|
465
618
|
return diff
|
|
466
619
|
|
|
467
|
-
|
|
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]:
|
|
620
|
+
def get_existing_version_info(self, version: str) -> Optional[dict]:
|
|
660
621
|
"""
|
|
661
622
|
Get the latest patch on an existing version by name in the application package.
|
|
662
623
|
Executes 'show versions like ... in application package' query and returns
|
|
@@ -664,9 +625,9 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
664
625
|
returns None.
|
|
665
626
|
"""
|
|
666
627
|
sql_executor = get_sql_executor()
|
|
667
|
-
with sql_executor.use_role(
|
|
628
|
+
with sql_executor.use_role(self.role):
|
|
668
629
|
try:
|
|
669
|
-
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}"
|
|
670
631
|
cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
671
632
|
|
|
672
633
|
if cursor.rowcount is None:
|
|
@@ -683,116 +644,75 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
683
644
|
|
|
684
645
|
except ProgrammingError as err:
|
|
685
646
|
if err.msg.__contains__("does not exist or not authorized"):
|
|
686
|
-
raise ApplicationPackageDoesNotExistError(
|
|
647
|
+
raise ApplicationPackageDoesNotExistError(self.name)
|
|
687
648
|
else:
|
|
688
|
-
generic_sql_error_handler(err=err
|
|
649
|
+
generic_sql_error_handler(err=err)
|
|
689
650
|
return None
|
|
690
651
|
|
|
691
|
-
@classmethod
|
|
692
652
|
def get_existing_release_directive_info_for_version(
|
|
693
|
-
|
|
694
|
-
package_name: str,
|
|
695
|
-
package_role: str,
|
|
696
|
-
version: str,
|
|
653
|
+
self, version: str
|
|
697
654
|
) -> List[dict]:
|
|
698
655
|
"""
|
|
699
656
|
Get all existing release directives, if present, set on the version defined in an application package.
|
|
700
657
|
It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
|
|
701
658
|
"""
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
|
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
|
+
]
|
|
720
667
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
)
|
|
668
|
+
def add_new_version(self, version: str, label: str | None = None) -> None:
|
|
669
|
+
"""
|
|
670
|
+
Add a new version with an optional label in application package.
|
|
671
|
+
"""
|
|
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
|
+
)
|
|
751
688
|
|
|
752
|
-
@classmethod
|
|
753
689
|
def add_new_patch_to_version(
|
|
754
|
-
|
|
755
|
-
console: AbstractConsole,
|
|
756
|
-
package_name: str,
|
|
757
|
-
package_role: str,
|
|
758
|
-
stage_fqn: str,
|
|
759
|
-
version: str,
|
|
760
|
-
patch: Optional[int] = None,
|
|
690
|
+
self, version: str, patch: int | None = None, label: str | None = None
|
|
761
691
|
):
|
|
762
692
|
"""
|
|
763
693
|
Add a new patch, optionally a custom one, to an existing version in an application package.
|
|
764
694
|
"""
|
|
765
|
-
|
|
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
|
-
)
|
|
695
|
+
console = self._workspace_ctx.console
|
|
782
696
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
+
)
|
|
788
713
|
|
|
789
|
-
@classmethod
|
|
790
714
|
def check_index_changes_in_git_repo(
|
|
791
|
-
|
|
792
|
-
console: AbstractConsole,
|
|
793
|
-
project_root: Path,
|
|
794
|
-
policy: PolicyBase,
|
|
795
|
-
is_interactive: bool,
|
|
715
|
+
self, policy: PolicyBase, interactive: bool
|
|
796
716
|
) -> None:
|
|
797
717
|
"""
|
|
798
718
|
Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
|
|
@@ -802,8 +722,10 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
802
722
|
from git import Repo
|
|
803
723
|
from git.exc import InvalidGitRepositoryError
|
|
804
724
|
|
|
725
|
+
console = self._workspace_ctx.console
|
|
726
|
+
|
|
805
727
|
try:
|
|
806
|
-
repo = Repo(project_root, search_parent_directories=True)
|
|
728
|
+
repo = Repo(self.project_root, search_parent_directories=True)
|
|
807
729
|
assert repo.git_dir is not None
|
|
808
730
|
|
|
809
731
|
# Check if the repo has any changes, including untracked files
|
|
@@ -819,7 +741,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
819
741
|
"Do you still want to continue?"
|
|
820
742
|
)
|
|
821
743
|
if not policy.should_proceed(user_prompt):
|
|
822
|
-
if
|
|
744
|
+
if interactive:
|
|
823
745
|
console.message("Not creating a new version.")
|
|
824
746
|
raise typer.Exit(0)
|
|
825
747
|
else:
|
|
@@ -831,141 +753,26 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
831
753
|
except InvalidGitRepositoryError:
|
|
832
754
|
pass # not a git repository, which is acceptable
|
|
833
755
|
|
|
834
|
-
|
|
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]:
|
|
756
|
+
def get_existing_app_pkg_info(self) -> Optional[dict]:
|
|
946
757
|
"""
|
|
947
758
|
Check for an existing application package by the same name as in project definition, in account.
|
|
948
759
|
It executes a 'show application packages like' query and returns the result as single row, if one exists.
|
|
949
760
|
"""
|
|
950
761
|
sql_executor = get_sql_executor()
|
|
951
|
-
with sql_executor.use_role(
|
|
762
|
+
with sql_executor.use_role(self.role):
|
|
952
763
|
return sql_executor.show_specific_object(
|
|
953
|
-
"application packages",
|
|
764
|
+
"application packages", self.name, name_col=NAME_COL
|
|
954
765
|
)
|
|
955
766
|
|
|
956
|
-
|
|
957
|
-
def get_app_pkg_distribution_in_snowflake(
|
|
958
|
-
package_name: str,
|
|
959
|
-
package_role: str,
|
|
960
|
-
) -> str:
|
|
767
|
+
def get_app_pkg_distribution_in_snowflake(self) -> str:
|
|
961
768
|
"""
|
|
962
769
|
Returns the 'distribution' attribute of a 'describe application package' SQL query, in lowercase.
|
|
963
770
|
"""
|
|
964
771
|
sql_executor = get_sql_executor()
|
|
965
|
-
with sql_executor.use_role(
|
|
772
|
+
with sql_executor.use_role(self.role):
|
|
966
773
|
try:
|
|
967
774
|
desc_cursor = sql_executor.execute_query(
|
|
968
|
-
f"describe application package {
|
|
775
|
+
f"describe application package {self.name}"
|
|
969
776
|
)
|
|
970
777
|
except ProgrammingError as err:
|
|
971
778
|
generic_sql_error_handler(err)
|
|
@@ -976,42 +783,34 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
976
783
|
for row in desc_cursor:
|
|
977
784
|
if row[0].lower() == "distribution":
|
|
978
785
|
return row[1].lower()
|
|
979
|
-
raise
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
'describe application package {package_name}'
|
|
984
|
-
"""
|
|
985
|
-
)
|
|
786
|
+
raise ObjectPropertyNotFoundError(
|
|
787
|
+
property_name="distribution",
|
|
788
|
+
object_type="application package",
|
|
789
|
+
object_name=self.name,
|
|
986
790
|
)
|
|
987
791
|
|
|
988
|
-
@classmethod
|
|
989
792
|
def verify_project_distribution(
|
|
990
|
-
|
|
991
|
-
console: AbstractConsole,
|
|
992
|
-
package_name: str,
|
|
993
|
-
package_role: str,
|
|
994
|
-
package_distribution: str,
|
|
793
|
+
self,
|
|
995
794
|
expected_distribution: Optional[str] = None,
|
|
996
795
|
) -> bool:
|
|
997
796
|
"""
|
|
998
797
|
Returns true if the 'distribution' attribute of an existing application package in snowflake
|
|
999
798
|
is the same as the the attribute specified in project definition file.
|
|
1000
799
|
"""
|
|
800
|
+
model = self._entity_model
|
|
801
|
+
workspace_ctx = self._workspace_ctx
|
|
802
|
+
|
|
1001
803
|
actual_distribution = (
|
|
1002
804
|
expected_distribution
|
|
1003
805
|
if expected_distribution
|
|
1004
|
-
else
|
|
1005
|
-
package_name=package_name,
|
|
1006
|
-
package_role=package_role,
|
|
1007
|
-
)
|
|
806
|
+
else self.get_app_pkg_distribution_in_snowflake()
|
|
1008
807
|
)
|
|
1009
|
-
project_def_distribution =
|
|
808
|
+
project_def_distribution = model.distribution.lower()
|
|
1010
809
|
if actual_distribution != project_def_distribution:
|
|
1011
|
-
console.warning(
|
|
810
|
+
workspace_ctx.console.warning(
|
|
1012
811
|
dedent(
|
|
1013
812
|
f"""\
|
|
1014
|
-
Application package {
|
|
813
|
+
Application package {self.name} in your Snowflake account has distribution property {actual_distribution},
|
|
1015
814
|
which does not match the value specified in project definition file: {project_def_distribution}.
|
|
1016
815
|
"""
|
|
1017
816
|
)
|
|
@@ -1019,104 +818,24 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1019
818
|
return False
|
|
1020
819
|
return True
|
|
1021
820
|
|
|
1022
|
-
|
|
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:
|
|
821
|
+
def create_app_package(self) -> None:
|
|
1095
822
|
"""
|
|
1096
823
|
Creates the application package with our up-to-date stage if none exists.
|
|
1097
824
|
"""
|
|
825
|
+
model = self._entity_model
|
|
826
|
+
console = self._workspace_ctx.console
|
|
1098
827
|
|
|
1099
|
-
# 1. Check for existing
|
|
1100
|
-
show_obj_row =
|
|
1101
|
-
package_name=package_name,
|
|
1102
|
-
package_role=package_role,
|
|
1103
|
-
)
|
|
828
|
+
# 1. Check for existing application package
|
|
829
|
+
show_obj_row = self.get_existing_app_pkg_info()
|
|
1104
830
|
|
|
1105
831
|
if show_obj_row:
|
|
1106
832
|
# 2. Check distribution of the existing application package
|
|
1107
|
-
actual_distribution =
|
|
1108
|
-
|
|
1109
|
-
|
|
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,
|
|
833
|
+
actual_distribution = self.get_app_pkg_distribution_in_snowflake()
|
|
834
|
+
if not self.verify_project_distribution(
|
|
835
|
+
expected_distribution=actual_distribution
|
|
1117
836
|
):
|
|
1118
837
|
console.warning(
|
|
1119
|
-
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}'."
|
|
1120
839
|
)
|
|
1121
840
|
|
|
1122
841
|
# 3. If actual_distribution is external, skip comment check
|
|
@@ -1124,92 +843,47 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1124
843
|
row_comment = show_obj_row[COMMENT_COL]
|
|
1125
844
|
|
|
1126
845
|
if row_comment not in ALLOWED_SPECIAL_COMMENTS:
|
|
1127
|
-
raise ApplicationPackageAlreadyExistsError(
|
|
846
|
+
raise ApplicationPackageAlreadyExistsError(self.name)
|
|
1128
847
|
|
|
1129
848
|
return
|
|
1130
849
|
|
|
1131
850
|
# If no application package pre-exists, create an application package, with the specified distribution in the project definition file.
|
|
1132
851
|
sql_executor = get_sql_executor()
|
|
1133
|
-
with sql_executor.use_role(
|
|
1134
|
-
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.")
|
|
1135
854
|
sql_executor.execute_query(
|
|
1136
855
|
dedent(
|
|
1137
856
|
f"""\
|
|
1138
|
-
create application package {
|
|
857
|
+
create application package {self.name}
|
|
1139
858
|
comment = {SPECIAL_COMMENT}
|
|
1140
|
-
distribution = {
|
|
859
|
+
distribution = {model.distribution}
|
|
1141
860
|
"""
|
|
1142
861
|
)
|
|
1143
862
|
)
|
|
1144
863
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
get_cli_context().metrics.set_counter_default(
|
|
1155
|
-
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,
|
|
1156
873
|
)
|
|
1157
874
|
|
|
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
875
|
def validate_setup_script(
|
|
1170
|
-
|
|
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,
|
|
876
|
+
self, use_scratch_stage: bool, interactive: bool, force: bool
|
|
1190
877
|
):
|
|
878
|
+
workspace_ctx = self._workspace_ctx
|
|
879
|
+
console = workspace_ctx.console
|
|
880
|
+
|
|
1191
881
|
"""Validates Native App setup script SQL."""
|
|
1192
882
|
with console.phase(f"Validating Snowflake Native App setup script."):
|
|
1193
|
-
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,
|
|
883
|
+
validation_result = self.get_validation_result(
|
|
1211
884
|
use_scratch_stage=use_scratch_stage,
|
|
1212
|
-
|
|
885
|
+
force=force,
|
|
886
|
+
interactive=interactive,
|
|
1213
887
|
)
|
|
1214
888
|
|
|
1215
889
|
# First print warnings, regardless of the outcome of validation
|
|
@@ -1226,53 +900,24 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1226
900
|
if validation_result["status"] == "FAIL":
|
|
1227
901
|
raise SetupScriptFailedValidation()
|
|
1228
902
|
|
|
1229
|
-
@classmethod
|
|
1230
903
|
def get_validation_result(
|
|
1231
|
-
|
|
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,
|
|
904
|
+
self, use_scratch_stage: bool, interactive: bool, force: bool
|
|
1251
905
|
):
|
|
1252
906
|
"""Call system$validate_native_app_setup() to validate deployed Native App setup script."""
|
|
907
|
+
stage_fqn = self.stage_fqn
|
|
1253
908
|
if use_scratch_stage:
|
|
1254
|
-
stage_fqn = scratch_stage_fqn
|
|
1255
|
-
|
|
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,
|
|
909
|
+
stage_fqn = self.scratch_stage_fqn
|
|
910
|
+
self._deploy(
|
|
1262
911
|
bundle_map=None,
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
prune=prune,
|
|
1267
|
-
recursive=recursive,
|
|
1268
|
-
paths=paths,
|
|
912
|
+
prune=True,
|
|
913
|
+
recursive=True,
|
|
914
|
+
paths=[],
|
|
1269
915
|
print_diff=False,
|
|
1270
916
|
validate=False,
|
|
1271
|
-
stage_fqn=
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
policy=policy,
|
|
917
|
+
stage_fqn=self.scratch_stage_fqn,
|
|
918
|
+
interactive=interactive,
|
|
919
|
+
force=force,
|
|
920
|
+
run_post_deploy_hooks=False,
|
|
1276
921
|
)
|
|
1277
922
|
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
|
|
1278
923
|
sql_executor = get_sql_executor()
|
|
@@ -1282,7 +927,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1282
927
|
)
|
|
1283
928
|
except ProgrammingError as err:
|
|
1284
929
|
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
1285
|
-
raise ApplicationPackageDoesNotExistError(
|
|
930
|
+
raise ApplicationPackageDoesNotExistError(self.name)
|
|
1286
931
|
generic_sql_error_handler(err)
|
|
1287
932
|
else:
|
|
1288
933
|
if not cursor.rowcount:
|
|
@@ -1290,103 +935,112 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1290
935
|
return json.loads(cursor.fetchone()[0])
|
|
1291
936
|
finally:
|
|
1292
937
|
if use_scratch_stage:
|
|
1293
|
-
console.step(
|
|
1294
|
-
|
|
938
|
+
self._workspace_ctx.console.step(
|
|
939
|
+
f"Dropping stage {self.scratch_stage_fqn}."
|
|
940
|
+
)
|
|
941
|
+
with sql_executor.use_role(self.role):
|
|
1295
942
|
sql_executor.execute_query(
|
|
1296
|
-
f"drop stage if exists {scratch_stage_fqn}"
|
|
943
|
+
f"drop stage if exists {self.scratch_stage_fqn}"
|
|
1297
944
|
)
|
|
1298
945
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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,
|
|
1306
954
|
):
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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)
|
|
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
|
|
1329
964
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
raise CouldNotDropApplicationPackageWithVersions(
|
|
1334
|
-
"Drop versions first, or use --force to override."
|
|
1335
|
-
)
|
|
965
|
+
resolved_version = None
|
|
966
|
+
resolved_patch = None
|
|
967
|
+
resolved_label = ""
|
|
1336
968
|
|
|
1337
|
-
#
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
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}'."
|
|
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."
|
|
1350
973
|
)
|
|
974
|
+
resolved_patch = patch
|
|
975
|
+
resolved_label = label if label is not None else ""
|
|
976
|
+
resolved_version = version
|
|
1351
977
|
|
|
1352
|
-
#
|
|
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
|
-
)
|
|
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.
|
|
1362
979
|
else:
|
|
1363
|
-
|
|
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(
|
|
980
|
+
console.message(
|
|
1370
981
|
dedent(
|
|
1371
982
|
f"""\
|
|
1372
|
-
|
|
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?
|
|
983
|
+
Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
|
|
1379
984
|
"""
|
|
1380
985
|
)
|
|
1381
986
|
)
|
|
1382
|
-
if
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
+
)
|
|
1385
998
|
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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,
|
|
1392
1046
|
)
|