snowflake-cli-labs 3.0.0rc2__py3-none-any.whl → 3.0.0rc3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
- snowflake/cli/_app/secret.py +9 -0
- snowflake/cli/_app/snow_connector.py +39 -27
- snowflake/cli/_plugins/git/manager.py +53 -7
- snowflake/cli/_plugins/helpers/commands.py +57 -0
- snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
- snowflake/cli/{api/entities → _plugins/nativeapp}/application_entity.py +18 -64
- snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_entity_model.py +2 -2
- snowflake/cli/{api/entities → _plugins/nativeapp}/application_package_entity.py +482 -33
- snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_package_entity_model.py +3 -3
- snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/commands.py +84 -16
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
- snowflake/cli/_plugins/nativeapp/manager.py +14 -9
- snowflake/cli/_plugins/nativeapp/policy.py +3 -0
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
- snowflake/cli/_plugins/nativeapp/run_processor.py +16 -19
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +6 -6
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +124 -88
- snowflake/cli/_plugins/nativeapp/version/commands.py +6 -24
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +35 -235
- snowflake/cli/_plugins/snowpark/commands.py +4 -4
- snowflake/cli/_plugins/snowpark/common.py +4 -4
- snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
- snowflake/cli/_plugins/stage/manager.py +9 -4
- snowflake/cli/_plugins/streamlit/commands.py +3 -3
- snowflake/cli/_plugins/streamlit/manager.py +8 -4
- snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
- snowflake/cli/_plugins/workspace/commands.py +83 -36
- snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
- snowflake/cli/api/commands/snow_typer.py +1 -1
- snowflake/cli/api/entities/common.py +3 -0
- snowflake/cli/api/entities/utils.py +0 -14
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/identifiers.py +4 -3
- snowflake/cli/api/project/definition_conversion.py +10 -9
- snowflake/cli/api/project/schemas/entities/common.py +17 -4
- snowflake/cli/api/project/schemas/entities/entities.py +13 -10
- snowflake/cli/api/project/schemas/project_definition.py +6 -6
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
- snowflake/cli/api/sql_execution.py +6 -15
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/METADATA +6 -6
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/RECORD +72 -67
- snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
- /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
- /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,8 +5,15 @@ from textwrap import dedent
|
|
|
5
5
|
from typing import Callable, List, Optional
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
|
-
from click import ClickException
|
|
9
|
-
from snowflake.cli._plugins.nativeapp.
|
|
8
|
+
from click import BadOptionUsage, ClickException
|
|
9
|
+
from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
|
|
10
|
+
ApplicationPackageEntityModel,
|
|
11
|
+
)
|
|
12
|
+
from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
13
|
+
BundleMap,
|
|
14
|
+
build_bundle,
|
|
15
|
+
find_version_info_in_manifest_file,
|
|
16
|
+
)
|
|
10
17
|
from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
|
|
11
18
|
from snowflake.cli._plugins.nativeapp.codegen.compiler import NativeAppCompiler
|
|
12
19
|
from snowflake.cli._plugins.nativeapp.constants import (
|
|
@@ -16,7 +23,9 @@ from snowflake.cli._plugins.nativeapp.constants import (
|
|
|
16
23
|
INTERNAL_DISTRIBUTION,
|
|
17
24
|
NAME_COL,
|
|
18
25
|
OWNER_COL,
|
|
26
|
+
PATCH_COL,
|
|
19
27
|
SPECIAL_COMMENT,
|
|
28
|
+
VERSION_COL,
|
|
20
29
|
)
|
|
21
30
|
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
22
31
|
ApplicationPackageAlreadyExistsError,
|
|
@@ -24,17 +33,23 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
|
24
33
|
CouldNotDropApplicationPackageWithVersions,
|
|
25
34
|
SetupScriptFailedValidation,
|
|
26
35
|
)
|
|
36
|
+
from snowflake.cli._plugins.nativeapp.policy import (
|
|
37
|
+
AllowAlwaysPolicy,
|
|
38
|
+
AskAlwaysPolicy,
|
|
39
|
+
DenyAlwaysPolicy,
|
|
40
|
+
PolicyBase,
|
|
41
|
+
)
|
|
27
42
|
from snowflake.cli._plugins.nativeapp.utils import (
|
|
28
43
|
needs_confirmation,
|
|
29
44
|
)
|
|
30
45
|
from snowflake.cli._plugins.stage.diff import DiffResult
|
|
31
46
|
from snowflake.cli._plugins.stage.manager import StageManager
|
|
32
47
|
from snowflake.cli._plugins.workspace.action_context import ActionContext
|
|
48
|
+
from snowflake.cli.api.console import cli_console as cc
|
|
33
49
|
from snowflake.cli.api.console.abc import AbstractConsole
|
|
34
50
|
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
35
51
|
from snowflake.cli.api.entities.utils import (
|
|
36
52
|
drop_generic_object,
|
|
37
|
-
ensure_correct_owner,
|
|
38
53
|
execute_post_deploy_hooks,
|
|
39
54
|
generic_sql_error_handler,
|
|
40
55
|
render_script_templates,
|
|
@@ -45,17 +60,20 @@ from snowflake.cli.api.errno import (
|
|
|
45
60
|
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
46
61
|
)
|
|
47
62
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
48
|
-
from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
|
|
49
|
-
ApplicationPackageEntityModel,
|
|
50
|
-
)
|
|
51
63
|
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
52
|
-
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
53
|
-
from snowflake.cli.api.project.util import
|
|
64
|
+
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
65
|
+
from snowflake.cli.api.project.util import (
|
|
66
|
+
extract_schema,
|
|
67
|
+
identifier_to_show_like_pattern,
|
|
68
|
+
to_identifier,
|
|
69
|
+
unquote_identifier,
|
|
70
|
+
)
|
|
54
71
|
from snowflake.cli.api.rendering.jinja import (
|
|
55
72
|
get_basic_jinja_env,
|
|
56
73
|
)
|
|
74
|
+
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
57
75
|
from snowflake.connector import ProgrammingError
|
|
58
|
-
from snowflake.connector.cursor import DictCursor
|
|
76
|
+
from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
59
77
|
|
|
60
78
|
|
|
61
79
|
class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
@@ -81,12 +99,22 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
81
99
|
recursive: bool,
|
|
82
100
|
paths: List[Path],
|
|
83
101
|
validate: bool,
|
|
102
|
+
interactive: bool,
|
|
103
|
+
force: bool,
|
|
84
104
|
stage_fqn: Optional[str] = None,
|
|
85
105
|
*args,
|
|
86
106
|
**kwargs,
|
|
87
107
|
):
|
|
88
108
|
model = self._entity_model
|
|
89
109
|
package_name = model.fqn.identifier
|
|
110
|
+
|
|
111
|
+
if force:
|
|
112
|
+
policy = AllowAlwaysPolicy()
|
|
113
|
+
elif interactive:
|
|
114
|
+
policy = AskAlwaysPolicy()
|
|
115
|
+
else:
|
|
116
|
+
policy = DenyAlwaysPolicy()
|
|
117
|
+
|
|
90
118
|
return self.deploy(
|
|
91
119
|
console=ctx.console,
|
|
92
120
|
project_root=ctx.project_root,
|
|
@@ -94,6 +122,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
94
122
|
bundle_root=Path(model.bundle_root),
|
|
95
123
|
generated_root=Path(model.generated_root),
|
|
96
124
|
artifacts=model.artifacts,
|
|
125
|
+
bundle_map=None,
|
|
97
126
|
package_name=package_name,
|
|
98
127
|
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
99
128
|
package_distribution=model.distribution,
|
|
@@ -108,6 +137,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
108
137
|
),
|
|
109
138
|
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
110
139
|
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
140
|
+
policy=policy,
|
|
111
141
|
)
|
|
112
142
|
|
|
113
143
|
def action_drop(self, ctx: ActionContext, force_drop: bool, *args, **kwargs):
|
|
@@ -125,7 +155,9 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
125
155
|
force_drop=force_drop,
|
|
126
156
|
)
|
|
127
157
|
|
|
128
|
-
def action_validate(
|
|
158
|
+
def action_validate(
|
|
159
|
+
self, ctx: ActionContext, interactive: bool, force: bool, *args, **kwargs
|
|
160
|
+
):
|
|
129
161
|
model = self._entity_model
|
|
130
162
|
package_name = model.fqn.identifier
|
|
131
163
|
stage_fqn = f"{package_name}.{model.stage}"
|
|
@@ -141,7 +173,9 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
141
173
|
recursive=True,
|
|
142
174
|
paths=[],
|
|
143
175
|
validate=False,
|
|
144
|
-
stage_fqn=model.scratch_stage,
|
|
176
|
+
stage_fqn=f"{package_name}.{model.scratch_stage}",
|
|
177
|
+
interactive=interactive,
|
|
178
|
+
force=force,
|
|
145
179
|
)
|
|
146
180
|
|
|
147
181
|
self.validate_setup_script(
|
|
@@ -150,11 +184,61 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
150
184
|
package_role=package_role,
|
|
151
185
|
stage_fqn=stage_fqn,
|
|
152
186
|
use_scratch_stage=True,
|
|
153
|
-
scratch_stage_fqn=model.scratch_stage,
|
|
187
|
+
scratch_stage_fqn=f"{package_name}.{model.scratch_stage}",
|
|
154
188
|
deploy_to_scratch_stage_fn=deploy_to_scratch_stage_fn,
|
|
155
189
|
)
|
|
156
190
|
ctx.console.message("Setup script is valid")
|
|
157
191
|
|
|
192
|
+
def action_version_list(
|
|
193
|
+
self, ctx: ActionContext, *args, **kwargs
|
|
194
|
+
) -> SnowflakeCursor:
|
|
195
|
+
model = self._entity_model
|
|
196
|
+
return self.version_list(
|
|
197
|
+
package_name=model.fqn.identifier,
|
|
198
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def action_version_create(
|
|
202
|
+
self,
|
|
203
|
+
ctx: ActionContext,
|
|
204
|
+
version: Optional[str],
|
|
205
|
+
patch: Optional[int],
|
|
206
|
+
skip_git_check: bool,
|
|
207
|
+
interactive: bool,
|
|
208
|
+
force: bool,
|
|
209
|
+
*args,
|
|
210
|
+
**kwargs,
|
|
211
|
+
):
|
|
212
|
+
model = self._entity_model
|
|
213
|
+
package_name = model.fqn.identifier
|
|
214
|
+
return self.version_create(
|
|
215
|
+
console=ctx.console,
|
|
216
|
+
project_root=ctx.project_root,
|
|
217
|
+
deploy_root=Path(model.deploy_root),
|
|
218
|
+
bundle_root=Path(model.bundle_root),
|
|
219
|
+
generated_root=Path(model.generated_root),
|
|
220
|
+
artifacts=model.artifacts,
|
|
221
|
+
package_name=package_name,
|
|
222
|
+
package_role=(model.meta and model.meta.role) or ctx.default_role,
|
|
223
|
+
package_distribution=model.distribution,
|
|
224
|
+
prune=True,
|
|
225
|
+
recursive=True,
|
|
226
|
+
paths=None,
|
|
227
|
+
print_diff=True,
|
|
228
|
+
validate=True,
|
|
229
|
+
stage_fqn=f"{package_name}.{model.stage}",
|
|
230
|
+
package_warehouse=(
|
|
231
|
+
(model.meta and model.meta.warehouse) or ctx.default_warehouse
|
|
232
|
+
),
|
|
233
|
+
post_deploy_hooks=model.meta and model.meta.post_deploy,
|
|
234
|
+
package_scripts=[], # Package scripts are not supported in PDFv2
|
|
235
|
+
version=version,
|
|
236
|
+
patch=patch,
|
|
237
|
+
skip_git_check=skip_git_check,
|
|
238
|
+
force=force,
|
|
239
|
+
interactive=interactive,
|
|
240
|
+
)
|
|
241
|
+
|
|
158
242
|
@staticmethod
|
|
159
243
|
def bundle(
|
|
160
244
|
project_root: Path,
|
|
@@ -186,21 +270,23 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
186
270
|
bundle_root: Path,
|
|
187
271
|
generated_root: Path,
|
|
188
272
|
artifacts: list[PathMapping],
|
|
273
|
+
bundle_map: BundleMap | None,
|
|
189
274
|
package_name: str,
|
|
190
275
|
package_role: str,
|
|
191
276
|
package_distribution: str,
|
|
192
277
|
package_warehouse: str | None,
|
|
193
278
|
prune: bool,
|
|
194
279
|
recursive: bool,
|
|
195
|
-
paths: List[Path],
|
|
280
|
+
paths: List[Path] | None,
|
|
196
281
|
print_diff: bool,
|
|
197
282
|
validate: bool,
|
|
198
283
|
stage_fqn: str,
|
|
199
284
|
post_deploy_hooks: list[PostDeployHook] | None,
|
|
200
285
|
package_scripts: List[str],
|
|
286
|
+
policy: PolicyBase,
|
|
201
287
|
) -> DiffResult:
|
|
202
|
-
# 1. Create a bundle
|
|
203
|
-
bundle_map = cls.bundle(
|
|
288
|
+
# 1. Create a bundle if one wasn't passed in
|
|
289
|
+
bundle_map = bundle_map or cls.bundle(
|
|
204
290
|
project_root=project_root,
|
|
205
291
|
deploy_root=deploy_root,
|
|
206
292
|
bundle_root=bundle_root,
|
|
@@ -210,13 +296,17 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
210
296
|
)
|
|
211
297
|
|
|
212
298
|
# 2. Create an empty application package, if none exists
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
299
|
+
try:
|
|
300
|
+
cls.create_app_package(
|
|
301
|
+
console=console,
|
|
302
|
+
package_name=package_name,
|
|
303
|
+
package_role=package_role,
|
|
304
|
+
package_distribution=package_distribution,
|
|
305
|
+
)
|
|
306
|
+
except ApplicationPackageAlreadyExistsError as e:
|
|
307
|
+
cc.warning(e.message)
|
|
308
|
+
if not policy.should_proceed("Proceed with using this package?"):
|
|
309
|
+
raise typer.Abort() from e
|
|
220
310
|
with get_sql_executor().use_role(package_role):
|
|
221
311
|
if package_scripts:
|
|
222
312
|
cls.apply_package_scripts(
|
|
@@ -266,6 +356,373 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
266
356
|
|
|
267
357
|
return diff
|
|
268
358
|
|
|
359
|
+
@staticmethod
|
|
360
|
+
def version_list(package_name: str, package_role: str) -> SnowflakeCursor:
|
|
361
|
+
"""
|
|
362
|
+
Get all existing versions, if defined, for an application package.
|
|
363
|
+
It executes a 'show versions in application package' query and returns all the results.
|
|
364
|
+
"""
|
|
365
|
+
sql_executor = get_sql_executor()
|
|
366
|
+
with sql_executor.use_role(package_role):
|
|
367
|
+
show_obj_query = f"show versions in application package {package_name}"
|
|
368
|
+
show_obj_cursor = sql_executor.execute_query(show_obj_query)
|
|
369
|
+
|
|
370
|
+
if show_obj_cursor.rowcount is None:
|
|
371
|
+
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
372
|
+
|
|
373
|
+
return show_obj_cursor
|
|
374
|
+
|
|
375
|
+
@classmethod
|
|
376
|
+
def version_create(
|
|
377
|
+
cls,
|
|
378
|
+
console: AbstractConsole,
|
|
379
|
+
project_root: Path,
|
|
380
|
+
deploy_root: Path,
|
|
381
|
+
bundle_root: Path,
|
|
382
|
+
generated_root: Path,
|
|
383
|
+
artifacts: list[PathMapping],
|
|
384
|
+
package_name: str,
|
|
385
|
+
package_role: str,
|
|
386
|
+
package_distribution: str,
|
|
387
|
+
package_warehouse: str | None,
|
|
388
|
+
prune: bool,
|
|
389
|
+
recursive: bool,
|
|
390
|
+
paths: List[Path] | None,
|
|
391
|
+
print_diff: bool,
|
|
392
|
+
validate: bool,
|
|
393
|
+
stage_fqn: str,
|
|
394
|
+
post_deploy_hooks: list[PostDeployHook] | None,
|
|
395
|
+
package_scripts: List[str],
|
|
396
|
+
version: Optional[str],
|
|
397
|
+
patch: Optional[int],
|
|
398
|
+
force: bool,
|
|
399
|
+
interactive: bool,
|
|
400
|
+
skip_git_check: bool,
|
|
401
|
+
):
|
|
402
|
+
"""
|
|
403
|
+
Perform bundle, application package creation, stage upload, version and/or patch to an application package.
|
|
404
|
+
"""
|
|
405
|
+
is_interactive = False
|
|
406
|
+
if force:
|
|
407
|
+
policy = AllowAlwaysPolicy()
|
|
408
|
+
elif interactive:
|
|
409
|
+
is_interactive = True
|
|
410
|
+
policy = AskAlwaysPolicy()
|
|
411
|
+
else:
|
|
412
|
+
policy = DenyAlwaysPolicy()
|
|
413
|
+
|
|
414
|
+
if skip_git_check:
|
|
415
|
+
git_policy = DenyAlwaysPolicy()
|
|
416
|
+
else:
|
|
417
|
+
git_policy = AllowAlwaysPolicy()
|
|
418
|
+
|
|
419
|
+
# Make sure version is not None before proceeding any further.
|
|
420
|
+
# This will raise an exception if version information is not found. Patch can be None.
|
|
421
|
+
bundle_map = None
|
|
422
|
+
if not version:
|
|
423
|
+
console.message(
|
|
424
|
+
dedent(
|
|
425
|
+
f"""\
|
|
426
|
+
Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
|
|
427
|
+
This step will bundle your app artifacts to determine the location of the manifest.yml file.
|
|
428
|
+
"""
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
bundle_map = cls.bundle(
|
|
432
|
+
project_root=project_root,
|
|
433
|
+
deploy_root=deploy_root,
|
|
434
|
+
bundle_root=bundle_root,
|
|
435
|
+
generated_root=generated_root,
|
|
436
|
+
artifacts=artifacts,
|
|
437
|
+
package_name=package_name,
|
|
438
|
+
)
|
|
439
|
+
version, patch = find_version_info_in_manifest_file(deploy_root)
|
|
440
|
+
if not version:
|
|
441
|
+
raise ClickException(
|
|
442
|
+
"Manifest.yml file does not contain a value for the version field."
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Check if --patch needs to throw a bad option error, either if application package does not exist or if version does not exist
|
|
446
|
+
if patch is not None:
|
|
447
|
+
try:
|
|
448
|
+
if not cls.get_existing_version_info(
|
|
449
|
+
version, package_name, package_role
|
|
450
|
+
):
|
|
451
|
+
raise BadOptionUsage(
|
|
452
|
+
option_name="patch",
|
|
453
|
+
message=f"Cannot create a custom patch when version {version} is not defined in the application package {package_name}. Try again without using --patch.",
|
|
454
|
+
)
|
|
455
|
+
except ApplicationPackageDoesNotExistError as app_err:
|
|
456
|
+
raise BadOptionUsage(
|
|
457
|
+
option_name="patch",
|
|
458
|
+
message=f"Cannot create a custom patch when application package {package_name} does not exist. Try again without using --patch.",
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
if git_policy.should_proceed():
|
|
462
|
+
cls.check_index_changes_in_git_repo(
|
|
463
|
+
console=console,
|
|
464
|
+
project_root=project_root,
|
|
465
|
+
policy=policy,
|
|
466
|
+
is_interactive=is_interactive,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
cls.deploy(
|
|
470
|
+
console=console,
|
|
471
|
+
project_root=project_root,
|
|
472
|
+
deploy_root=deploy_root,
|
|
473
|
+
bundle_root=bundle_root,
|
|
474
|
+
generated_root=generated_root,
|
|
475
|
+
artifacts=artifacts,
|
|
476
|
+
bundle_map=bundle_map,
|
|
477
|
+
package_name=package_name,
|
|
478
|
+
package_role=package_role,
|
|
479
|
+
package_distribution=package_distribution,
|
|
480
|
+
prune=prune,
|
|
481
|
+
recursive=recursive,
|
|
482
|
+
paths=paths,
|
|
483
|
+
print_diff=print_diff,
|
|
484
|
+
validate=validate,
|
|
485
|
+
stage_fqn=stage_fqn,
|
|
486
|
+
package_warehouse=package_warehouse,
|
|
487
|
+
post_deploy_hooks=post_deploy_hooks,
|
|
488
|
+
package_scripts=package_scripts,
|
|
489
|
+
policy=policy,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Warn if the version exists in a release directive(s)
|
|
493
|
+
existing_release_directives = (
|
|
494
|
+
cls.get_existing_release_directive_info_for_version(
|
|
495
|
+
package_name, package_role, version
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
if existing_release_directives:
|
|
500
|
+
release_directive_names = ", ".join(
|
|
501
|
+
row["name"] for row in existing_release_directives
|
|
502
|
+
)
|
|
503
|
+
console.warning(
|
|
504
|
+
dedent(
|
|
505
|
+
f"""\
|
|
506
|
+
Version {version} already defined in application package {package_name} and in release directive(s): {release_directive_names}.
|
|
507
|
+
"""
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
user_prompt = (
|
|
512
|
+
f"Are you sure you want to create a new patch for version {version} in application "
|
|
513
|
+
f"package {package_name}? Once added, this operation cannot be undone."
|
|
514
|
+
)
|
|
515
|
+
if not policy.should_proceed(user_prompt):
|
|
516
|
+
if is_interactive:
|
|
517
|
+
console.message("Not creating a new patch.")
|
|
518
|
+
raise typer.Exit(0)
|
|
519
|
+
else:
|
|
520
|
+
console.message(
|
|
521
|
+
"Cannot create a new patch non-interactively without --force."
|
|
522
|
+
)
|
|
523
|
+
raise typer.Exit(1)
|
|
524
|
+
|
|
525
|
+
# Define a new version in the application package
|
|
526
|
+
if not cls.get_existing_version_info(version, package_name, package_role):
|
|
527
|
+
cls.add_new_version(
|
|
528
|
+
console=console,
|
|
529
|
+
package_name=package_name,
|
|
530
|
+
package_role=package_role,
|
|
531
|
+
stage_fqn=stage_fqn,
|
|
532
|
+
version=version,
|
|
533
|
+
)
|
|
534
|
+
return # A new version created automatically has patch 0, we do not need to further increment the patch.
|
|
535
|
+
|
|
536
|
+
# Add a new patch to an existing (old) version
|
|
537
|
+
cls.add_new_patch_to_version(
|
|
538
|
+
console=console,
|
|
539
|
+
package_name=package_name,
|
|
540
|
+
package_role=package_role,
|
|
541
|
+
stage_fqn=stage_fqn,
|
|
542
|
+
version=version,
|
|
543
|
+
patch=patch,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def get_existing_version_info(
|
|
548
|
+
version: str,
|
|
549
|
+
package_name: str,
|
|
550
|
+
package_role: str,
|
|
551
|
+
) -> Optional[dict]:
|
|
552
|
+
"""
|
|
553
|
+
Get the latest patch on an existing version by name in the application package.
|
|
554
|
+
Executes 'show versions like ... in application package' query and returns
|
|
555
|
+
the latest patch in the version as a single row, if one exists. Otherwise,
|
|
556
|
+
returns None.
|
|
557
|
+
"""
|
|
558
|
+
sql_executor = get_sql_executor()
|
|
559
|
+
with sql_executor.use_role(package_role):
|
|
560
|
+
try:
|
|
561
|
+
query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
|
|
562
|
+
cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
563
|
+
|
|
564
|
+
if cursor.rowcount is None:
|
|
565
|
+
raise SnowflakeSQLExecutionError(query)
|
|
566
|
+
|
|
567
|
+
matching_rows = find_all_rows(
|
|
568
|
+
cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
if not matching_rows:
|
|
572
|
+
return None
|
|
573
|
+
|
|
574
|
+
return max(matching_rows, key=lambda row: row[PATCH_COL])
|
|
575
|
+
|
|
576
|
+
except ProgrammingError as err:
|
|
577
|
+
if err.msg.__contains__("does not exist or not authorized"):
|
|
578
|
+
raise ApplicationPackageDoesNotExistError(package_name)
|
|
579
|
+
else:
|
|
580
|
+
generic_sql_error_handler(err=err, role=package_role)
|
|
581
|
+
return None
|
|
582
|
+
|
|
583
|
+
@classmethod
|
|
584
|
+
def get_existing_release_directive_info_for_version(
|
|
585
|
+
cls,
|
|
586
|
+
package_name: str,
|
|
587
|
+
package_role: str,
|
|
588
|
+
version: str,
|
|
589
|
+
) -> List[dict]:
|
|
590
|
+
"""
|
|
591
|
+
Get all existing release directives, if present, set on the version defined in an application package.
|
|
592
|
+
It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
|
|
593
|
+
"""
|
|
594
|
+
sql_executor = get_sql_executor()
|
|
595
|
+
with sql_executor.use_role(package_role):
|
|
596
|
+
show_obj_query = (
|
|
597
|
+
f"show release directives in application package {package_name}"
|
|
598
|
+
)
|
|
599
|
+
show_obj_cursor = sql_executor.execute_query(
|
|
600
|
+
show_obj_query, cursor_class=DictCursor
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
if show_obj_cursor.rowcount is None:
|
|
604
|
+
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
605
|
+
|
|
606
|
+
show_obj_rows = find_all_rows(
|
|
607
|
+
show_obj_cursor,
|
|
608
|
+
lambda row: row[VERSION_COL] == unquote_identifier(version),
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
return show_obj_rows
|
|
612
|
+
|
|
613
|
+
@classmethod
|
|
614
|
+
def add_new_version(
|
|
615
|
+
cls,
|
|
616
|
+
console: AbstractConsole,
|
|
617
|
+
package_name: str,
|
|
618
|
+
package_role: str,
|
|
619
|
+
stage_fqn: str,
|
|
620
|
+
version: str,
|
|
621
|
+
) -> None:
|
|
622
|
+
"""
|
|
623
|
+
Defines a new version in an existing application package.
|
|
624
|
+
"""
|
|
625
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
626
|
+
version = to_identifier(version)
|
|
627
|
+
sql_executor = get_sql_executor()
|
|
628
|
+
with sql_executor.use_role(package_role):
|
|
629
|
+
console.step(
|
|
630
|
+
f"Defining a new version {version} in application package {package_name}"
|
|
631
|
+
)
|
|
632
|
+
add_version_query = dedent(
|
|
633
|
+
f"""\
|
|
634
|
+
alter application package {package_name}
|
|
635
|
+
add version {version}
|
|
636
|
+
using @{stage_fqn}
|
|
637
|
+
"""
|
|
638
|
+
)
|
|
639
|
+
sql_executor.execute_query(add_version_query, cursor_class=DictCursor)
|
|
640
|
+
console.message(
|
|
641
|
+
f"Version {version} created for application package {package_name}."
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
@classmethod
|
|
645
|
+
def add_new_patch_to_version(
|
|
646
|
+
cls,
|
|
647
|
+
console: AbstractConsole,
|
|
648
|
+
package_name: str,
|
|
649
|
+
package_role: str,
|
|
650
|
+
stage_fqn: str,
|
|
651
|
+
version: str,
|
|
652
|
+
patch: Optional[int] = None,
|
|
653
|
+
):
|
|
654
|
+
"""
|
|
655
|
+
Add a new patch, optionally a custom one, to an existing version in an application package.
|
|
656
|
+
"""
|
|
657
|
+
# Make the version a valid identifier, adding quotes if necessary
|
|
658
|
+
version = to_identifier(version)
|
|
659
|
+
sql_executor = get_sql_executor()
|
|
660
|
+
with sql_executor.use_role(package_role):
|
|
661
|
+
console.step(
|
|
662
|
+
f"Adding new patch to version {version} defined in application package {package_name}"
|
|
663
|
+
)
|
|
664
|
+
add_version_query = dedent(
|
|
665
|
+
f"""\
|
|
666
|
+
alter application package {package_name}
|
|
667
|
+
add patch {patch if patch else ""} for version {version}
|
|
668
|
+
using @{stage_fqn}
|
|
669
|
+
"""
|
|
670
|
+
)
|
|
671
|
+
result_cursor = sql_executor.execute_query(
|
|
672
|
+
add_version_query, cursor_class=DictCursor
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
show_row = result_cursor.fetchall()[0]
|
|
676
|
+
new_patch = show_row["patch"]
|
|
677
|
+
console.message(
|
|
678
|
+
f"Patch {new_patch} created for version {version} defined in application package {package_name}."
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
@classmethod
|
|
682
|
+
def check_index_changes_in_git_repo(
|
|
683
|
+
cls,
|
|
684
|
+
console: AbstractConsole,
|
|
685
|
+
project_root: Path,
|
|
686
|
+
policy: PolicyBase,
|
|
687
|
+
is_interactive: bool,
|
|
688
|
+
) -> None:
|
|
689
|
+
"""
|
|
690
|
+
Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
|
|
691
|
+
it also checks if there any local changes to the directory that may not be on the application package stage.
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
from git import Repo
|
|
695
|
+
from git.exc import InvalidGitRepositoryError
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
repo = Repo(project_root, search_parent_directories=True)
|
|
699
|
+
assert repo.git_dir is not None
|
|
700
|
+
|
|
701
|
+
# Check if the repo has any changes, including untracked files
|
|
702
|
+
if repo.is_dirty(untracked_files=True):
|
|
703
|
+
console.warning(
|
|
704
|
+
"Changes detected in the git repository. "
|
|
705
|
+
"(Rerun your command with --skip-git-check flag to ignore this check)"
|
|
706
|
+
)
|
|
707
|
+
repo.git.execute(["git", "status"])
|
|
708
|
+
|
|
709
|
+
user_prompt = (
|
|
710
|
+
"You have local changes in this repository that are not part of a previous commit. "
|
|
711
|
+
"Do you still want to continue?"
|
|
712
|
+
)
|
|
713
|
+
if not policy.should_proceed(user_prompt):
|
|
714
|
+
if is_interactive:
|
|
715
|
+
console.message("Not creating a new version.")
|
|
716
|
+
raise typer.Exit(0)
|
|
717
|
+
else:
|
|
718
|
+
console.message(
|
|
719
|
+
"Cannot create a new version non-interactively without --force."
|
|
720
|
+
)
|
|
721
|
+
raise typer.Exit(1)
|
|
722
|
+
|
|
723
|
+
except InvalidGitRepositoryError:
|
|
724
|
+
pass # not a git repository, which is acceptable
|
|
725
|
+
|
|
269
726
|
@staticmethod
|
|
270
727
|
def get_existing_app_pkg_info(
|
|
271
728
|
package_name: str,
|
|
@@ -424,11 +881,6 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
424
881
|
)
|
|
425
882
|
|
|
426
883
|
if show_obj_row:
|
|
427
|
-
# 1. Check for the right owner role
|
|
428
|
-
ensure_correct_owner(
|
|
429
|
-
row=show_obj_row, role=package_role, obj_name=package_name
|
|
430
|
-
)
|
|
431
|
-
|
|
432
884
|
# 2. Check distribution of the existing application package
|
|
433
885
|
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
434
886
|
package_name=package_name,
|
|
@@ -581,11 +1033,8 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
581
1033
|
)
|
|
582
1034
|
return
|
|
583
1035
|
|
|
584
|
-
# 2. Check for the right owner
|
|
585
|
-
ensure_correct_owner(row=show_obj_row, role=package_role, obj_name=package_name)
|
|
586
|
-
|
|
587
1036
|
with sql_executor.use_role(package_role):
|
|
588
|
-
#
|
|
1037
|
+
# 2. Check for versions in the application package
|
|
589
1038
|
show_versions_query = f"show versions in application package {package_name}"
|
|
590
1039
|
show_versions_cursor = sql_executor.execute_query(
|
|
591
1040
|
show_versions_query, cursor_class=DictCursor
|
|
@@ -600,7 +1049,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
600
1049
|
"Drop versions first, or use --force to override."
|
|
601
1050
|
)
|
|
602
1051
|
|
|
603
|
-
#
|
|
1052
|
+
# 3. Check distribution of the existing application package
|
|
604
1053
|
actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
|
|
605
1054
|
package_name=package_name,
|
|
606
1055
|
package_role=package_role,
|
|
@@ -615,7 +1064,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
615
1064
|
f"Dropping application package {package_name} with distribution '{actual_distribution}'."
|
|
616
1065
|
)
|
|
617
1066
|
|
|
618
|
-
#
|
|
1067
|
+
# 4. If distribution is internal, check if created by the Snowflake CLI
|
|
619
1068
|
row_comment = show_obj_row[COMMENT_COL]
|
|
620
1069
|
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
621
1070
|
if row_comment in ALLOWED_SPECIAL_COMMENTS:
|
|
@@ -19,14 +19,14 @@ from typing import List, Literal, Optional, Union
|
|
|
19
19
|
from pydantic import Field, field_validator
|
|
20
20
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
21
21
|
EntityModelBase,
|
|
22
|
+
Identifier,
|
|
22
23
|
)
|
|
23
|
-
from snowflake.cli.api.project.schemas.identifier_model import Identifier
|
|
24
|
-
from snowflake.cli.api.project.schemas.native_app.package import DistributionOptions
|
|
25
|
-
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
26
24
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
27
25
|
DiscriminatorField,
|
|
28
26
|
IdentifierField,
|
|
29
27
|
)
|
|
28
|
+
from snowflake.cli.api.project.schemas.v1.native_app.package import DistributionOptions
|
|
29
|
+
from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
|
|
30
30
|
from snowflake.cli.api.project.util import append_test_resource_suffix
|
|
31
31
|
|
|
32
32
|
|