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
snowflake/cli/__about__.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
from snowflake.cli._plugins.connection import plugin_spec as connection_plugin_spec
|
|
16
16
|
from snowflake.cli._plugins.cortex import plugin_spec as cortex_plugin_spec
|
|
17
17
|
from snowflake.cli._plugins.git import plugin_spec as git_plugin_spec
|
|
18
|
+
from snowflake.cli._plugins.helpers import plugin_spec as migrate_plugin_spec
|
|
18
19
|
from snowflake.cli._plugins.init import plugin_spec as init_plugin_spec
|
|
19
20
|
from snowflake.cli._plugins.nativeapp import plugin_spec as nativeapp_plugin_spec
|
|
20
21
|
from snowflake.cli._plugins.notebook import plugin_spec as notebook_plugin_spec
|
|
@@ -31,6 +32,7 @@ from snowflake.cli._plugins.workspace import plugin_spec as workspace_plugin_spe
|
|
|
31
32
|
def get_builtin_plugin_name_to_plugin_spec():
|
|
32
33
|
plugin_specs = {
|
|
33
34
|
"connection": connection_plugin_spec,
|
|
35
|
+
"helpers": migrate_plugin_spec,
|
|
34
36
|
"spcs": spcs_plugin_spec,
|
|
35
37
|
"nativeapp": nativeapp_plugin_spec,
|
|
36
38
|
"object": object_plugin_spec,
|
|
@@ -24,6 +24,7 @@ from click.exceptions import ClickException
|
|
|
24
24
|
from snowflake.cli._app.constants import (
|
|
25
25
|
PARAM_APPLICATION_NAME,
|
|
26
26
|
)
|
|
27
|
+
from snowflake.cli._app.secret import SecretType
|
|
27
28
|
from snowflake.cli._app.telemetry import command_info
|
|
28
29
|
from snowflake.cli.api.config import (
|
|
29
30
|
get_connection_dict,
|
|
@@ -205,7 +206,7 @@ def _load_private_key(connection_parameters: Dict, private_key_var_name: str) ->
|
|
|
205
206
|
connection_parameters[private_key_var_name]
|
|
206
207
|
)
|
|
207
208
|
private_key = _load_pem_to_der(private_key_pem)
|
|
208
|
-
connection_parameters["private_key"] = private_key
|
|
209
|
+
connection_parameters["private_key"] = private_key.value
|
|
209
210
|
del connection_parameters[private_key_var_name]
|
|
210
211
|
else:
|
|
211
212
|
raise ClickException(
|
|
@@ -217,10 +218,11 @@ def _load_private_key_from_parameters(
|
|
|
217
218
|
connection_parameters: Dict, private_key_var_name: str
|
|
218
219
|
) -> None:
|
|
219
220
|
if connection_parameters.get("authenticator") == "SNOWFLAKE_JWT":
|
|
220
|
-
private_key_pem =
|
|
221
|
-
|
|
221
|
+
private_key_pem = _load_pem_from_parameters(
|
|
222
|
+
connection_parameters[private_key_var_name]
|
|
223
|
+
)
|
|
222
224
|
private_key = _load_pem_to_der(private_key_pem)
|
|
223
|
-
connection_parameters["private_key"] = private_key
|
|
225
|
+
connection_parameters["private_key"] = private_key.value
|
|
224
226
|
del connection_parameters[private_key_var_name]
|
|
225
227
|
else:
|
|
226
228
|
raise ClickException(
|
|
@@ -236,43 +238,49 @@ def _update_connection_application_name(connection_parameters: Dict):
|
|
|
236
238
|
connection_parameters.update(connection_application_params)
|
|
237
239
|
|
|
238
240
|
|
|
239
|
-
def _load_pem_from_file(private_key_file: str) ->
|
|
241
|
+
def _load_pem_from_file(private_key_file: str) -> SecretType:
|
|
240
242
|
with SecurePath(private_key_file).open(
|
|
241
243
|
"rb", read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB
|
|
242
244
|
) as f:
|
|
243
|
-
private_key_pem = f.read()
|
|
245
|
+
private_key_pem = SecretType(f.read())
|
|
244
246
|
return private_key_pem
|
|
245
247
|
|
|
246
248
|
|
|
247
|
-
def
|
|
249
|
+
def _load_pem_from_parameters(private_key_raw: str) -> SecretType:
|
|
250
|
+
return SecretType(private_key_raw.encode("utf-8"))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _load_pem_to_der(private_key_pem: SecretType) -> SecretType:
|
|
248
254
|
"""
|
|
249
255
|
Given a private key file path (in PEM format), decode key data into DER
|
|
250
256
|
format
|
|
251
257
|
"""
|
|
252
|
-
private_key_passphrase = os.getenv("PRIVATE_KEY_PASSPHRASE", None)
|
|
258
|
+
private_key_passphrase = SecretType(os.getenv("PRIVATE_KEY_PASSPHRASE", None))
|
|
253
259
|
if (
|
|
254
|
-
private_key_pem.startswith(ENCRYPTED_PKCS8_PK_HEADER)
|
|
255
|
-
and private_key_passphrase is None
|
|
260
|
+
private_key_pem.value.startswith(ENCRYPTED_PKCS8_PK_HEADER)
|
|
261
|
+
and private_key_passphrase.value is None
|
|
256
262
|
):
|
|
257
263
|
raise ClickException(
|
|
258
264
|
"Encrypted private key, you must provide the"
|
|
259
265
|
"passphrase in the environment variable PRIVATE_KEY_PASSPHRASE"
|
|
260
266
|
)
|
|
261
267
|
|
|
262
|
-
if not private_key_pem.startswith(
|
|
268
|
+
if not private_key_pem.value.startswith(
|
|
263
269
|
ENCRYPTED_PKCS8_PK_HEADER
|
|
264
|
-
) and not private_key_pem.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
|
|
270
|
+
) and not private_key_pem.value.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
|
|
265
271
|
raise ClickException(
|
|
266
272
|
"Private key provided is not in PKCS#8 format. Please use correct format."
|
|
267
273
|
)
|
|
268
274
|
|
|
269
|
-
if private_key_pem.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
|
|
270
|
-
private_key_passphrase = None
|
|
275
|
+
if private_key_pem.value.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
|
|
276
|
+
private_key_passphrase = SecretType(None)
|
|
271
277
|
|
|
272
278
|
return prepare_private_key(private_key_pem, private_key_passphrase)
|
|
273
279
|
|
|
274
280
|
|
|
275
|
-
def prepare_private_key(
|
|
281
|
+
def prepare_private_key(
|
|
282
|
+
private_key_pem: SecretType, private_key_passphrase: SecretType = SecretType(None)
|
|
283
|
+
):
|
|
276
284
|
from cryptography.hazmat.backends import default_backend
|
|
277
285
|
from cryptography.hazmat.primitives.serialization import (
|
|
278
286
|
Encoding,
|
|
@@ -281,17 +289,21 @@ def prepare_private_key(private_key_pem, private_key_passphrase=None):
|
|
|
281
289
|
load_pem_private_key,
|
|
282
290
|
)
|
|
283
291
|
|
|
284
|
-
private_key =
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
+
private_key = SecretType(
|
|
293
|
+
load_pem_private_key(
|
|
294
|
+
private_key_pem.value,
|
|
295
|
+
(
|
|
296
|
+
str.encode(private_key_passphrase.value)
|
|
297
|
+
if private_key_passphrase.value is not None
|
|
298
|
+
else private_key_passphrase.value
|
|
299
|
+
),
|
|
300
|
+
default_backend(),
|
|
301
|
+
)
|
|
292
302
|
)
|
|
293
|
-
return
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
303
|
+
return SecretType(
|
|
304
|
+
private_key.value.private_bytes(
|
|
305
|
+
encoding=Encoding.DER,
|
|
306
|
+
format=PrivateFormat.PKCS8,
|
|
307
|
+
encryption_algorithm=NoEncryption(),
|
|
308
|
+
)
|
|
297
309
|
)
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from pathlib import Path
|
|
17
|
+
from pathlib import Path, PurePosixPath
|
|
18
18
|
from textwrap import dedent
|
|
19
19
|
from typing import List
|
|
20
20
|
|
|
21
|
+
from click import UsageError
|
|
21
22
|
from snowflake.cli._plugins.stage.manager import (
|
|
22
23
|
USER_STAGE_PREFIX,
|
|
23
24
|
StageManager,
|
|
@@ -27,16 +28,22 @@ from snowflake.cli._plugins.stage.manager import (
|
|
|
27
28
|
from snowflake.cli.api.identifiers import FQN
|
|
28
29
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
29
30
|
|
|
31
|
+
# Replace magic numbers with constants
|
|
32
|
+
OMIT_FIRST = slice(1, None)
|
|
33
|
+
OMIT_STAGE = slice(3, None)
|
|
34
|
+
OMIT_STAGE_IN_NEW_LIST_FILES = slice(2, None)
|
|
35
|
+
ONLY_STAGE = slice(3)
|
|
36
|
+
|
|
30
37
|
|
|
31
38
|
class GitStagePathParts(StagePathParts):
|
|
32
39
|
def __init__(self, stage_path: str):
|
|
33
40
|
self.stage = GitManager.get_stage_from_path(stage_path)
|
|
34
|
-
stage_path_parts =
|
|
41
|
+
stage_path_parts = GitManager.split_git_path(stage_path)
|
|
35
42
|
git_repo_name = stage_path_parts[0].split(".")[-1]
|
|
36
43
|
if git_repo_name.startswith("@"):
|
|
37
|
-
git_repo_name = git_repo_name[
|
|
44
|
+
git_repo_name = git_repo_name[OMIT_FIRST]
|
|
38
45
|
self.stage_name = "/".join([git_repo_name, *stage_path_parts[1:3], ""])
|
|
39
|
-
self.directory = "/".join(stage_path_parts[
|
|
46
|
+
self.directory = "/".join(stage_path_parts[OMIT_STAGE])
|
|
40
47
|
self.is_directory = True if stage_path.endswith("/") else False
|
|
41
48
|
|
|
42
49
|
@property
|
|
@@ -45,7 +52,12 @@ class GitStagePathParts(StagePathParts):
|
|
|
45
52
|
|
|
46
53
|
@classmethod
|
|
47
54
|
def get_directory(cls, stage_path: str) -> str:
|
|
48
|
-
|
|
55
|
+
git_path_parts = GitManager.split_git_path(stage_path)
|
|
56
|
+
# New file list does not have a stage name at the beginning
|
|
57
|
+
if stage_path.startswith("/"):
|
|
58
|
+
return "/".join(git_path_parts[OMIT_STAGE_IN_NEW_LIST_FILES])
|
|
59
|
+
else:
|
|
60
|
+
return "/".join(git_path_parts[OMIT_STAGE])
|
|
49
61
|
|
|
50
62
|
@property
|
|
51
63
|
def full_path(self) -> str:
|
|
@@ -53,7 +65,7 @@ class GitStagePathParts(StagePathParts):
|
|
|
53
65
|
|
|
54
66
|
def replace_stage_prefix(self, file_path: str) -> str:
|
|
55
67
|
stage = Path(self.stage).parts[0]
|
|
56
|
-
file_path_without_prefix = Path(file_path).parts[
|
|
68
|
+
file_path_without_prefix = Path(file_path).parts[OMIT_FIRST]
|
|
57
69
|
return f"{stage}/{'/'.join(file_path_without_prefix)}"
|
|
58
70
|
|
|
59
71
|
def add_stage_prefix(self, file_path: str) -> str:
|
|
@@ -95,7 +107,8 @@ class GitManager(StageManager):
|
|
|
95
107
|
Returns stage name from potential path on stage. For example
|
|
96
108
|
repo/branches/main/foo/bar -> repo/branches/main/
|
|
97
109
|
"""
|
|
98
|
-
|
|
110
|
+
path_parts = GitManager.split_git_path(path)
|
|
111
|
+
return f"{'/'.join(path_parts[ONLY_STAGE])}/"
|
|
99
112
|
|
|
100
113
|
@staticmethod
|
|
101
114
|
def _stage_path_part_factory(stage_path: str) -> StagePathParts:
|
|
@@ -103,3 +116,36 @@ class GitManager(StageManager):
|
|
|
103
116
|
if stage_path.startswith(USER_STAGE_PREFIX):
|
|
104
117
|
return UserStagePathParts(stage_path)
|
|
105
118
|
return GitStagePathParts(stage_path)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def split_git_path(path: str):
|
|
122
|
+
# Check if path contains quotes and split it accordingly
|
|
123
|
+
if '/"' in path and '"/' in path:
|
|
124
|
+
if path.count('"') > 2:
|
|
125
|
+
raise UsageError(
|
|
126
|
+
f'Invalid string {path}, too much " in path, expected 2.'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
path_parts = path.split('"')
|
|
130
|
+
before_quoted_part = GitManager._split_path_without_empty_parts(
|
|
131
|
+
path_parts[0]
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if path_parts[2] == "/":
|
|
135
|
+
after_quoted_part = []
|
|
136
|
+
else:
|
|
137
|
+
after_quoted_part = GitManager._split_path_without_empty_parts(
|
|
138
|
+
path_parts[2]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return [
|
|
142
|
+
*before_quoted_part,
|
|
143
|
+
f'"{path_parts[1]}"',
|
|
144
|
+
*after_quoted_part,
|
|
145
|
+
]
|
|
146
|
+
else:
|
|
147
|
+
return GitManager._split_path_without_empty_parts(path)
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _split_path_without_empty_parts(path: str):
|
|
151
|
+
return [e for e in PurePosixPath(path).parts if e != "/"]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
import yaml
|
|
19
|
+
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
20
|
+
from snowflake.cli.api.output.types import MessageResult
|
|
21
|
+
from snowflake.cli.api.project.definition_conversion import (
|
|
22
|
+
convert_project_definition_to_v2,
|
|
23
|
+
)
|
|
24
|
+
from snowflake.cli.api.project.definition_manager import DefinitionManager
|
|
25
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
26
|
+
|
|
27
|
+
app = SnowTyperFactory(
|
|
28
|
+
name="helpers",
|
|
29
|
+
help="Helper commands.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def v1_to_v2(
|
|
35
|
+
accept_templates: bool = typer.Option(
|
|
36
|
+
False, "-t", "--accept-templates", help="Allows the migration of templates."
|
|
37
|
+
),
|
|
38
|
+
**options,
|
|
39
|
+
):
|
|
40
|
+
"""Migrates the Snowpark, Streamlit, and Native App project definition files from V1 to V2."""
|
|
41
|
+
manager = DefinitionManager()
|
|
42
|
+
pd = manager.unrendered_project_definition
|
|
43
|
+
|
|
44
|
+
if pd.meets_version_requirement("2"):
|
|
45
|
+
return MessageResult("Project definition is already at version 2.")
|
|
46
|
+
|
|
47
|
+
pd_v2 = convert_project_definition_to_v2(manager.project_root, pd, accept_templates)
|
|
48
|
+
|
|
49
|
+
SecurePath("snowflake.yml").rename("snowflake_V1.yml")
|
|
50
|
+
with open("snowflake.yml", "w") as file:
|
|
51
|
+
yaml.dump(
|
|
52
|
+
pd_v2.model_dump(
|
|
53
|
+
exclude_unset=True, exclude_none=True, mode="json", by_alias=True
|
|
54
|
+
),
|
|
55
|
+
file,
|
|
56
|
+
)
|
|
57
|
+
return MessageResult("Project definition migrated to version 2.")
|
|
@@ -11,3 +11,20 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from snowflake.cli._plugins.helpers import commands
|
|
16
|
+
from snowflake.cli.api.plugins.command import (
|
|
17
|
+
SNOWCLI_ROOT_COMMAND_PATH,
|
|
18
|
+
CommandSpec,
|
|
19
|
+
CommandType,
|
|
20
|
+
plugin_hook_impl,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@plugin_hook_impl
|
|
25
|
+
def command_spec():
|
|
26
|
+
return CommandSpec(
|
|
27
|
+
parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
|
|
28
|
+
command_type=CommandType.COMMAND_GROUP,
|
|
29
|
+
typer_instance=commands.app.create_instance(),
|
|
30
|
+
)
|
|
@@ -5,6 +5,15 @@ from typing import Callable, List, Optional, TypedDict
|
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
from click import ClickException, UsageError
|
|
8
|
+
from snowflake.cli._plugins.nativeapp.application_entity_model import (
|
|
9
|
+
ApplicationEntityModel,
|
|
10
|
+
)
|
|
11
|
+
from snowflake.cli._plugins.nativeapp.application_package_entity import (
|
|
12
|
+
ApplicationPackageEntity,
|
|
13
|
+
)
|
|
14
|
+
from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
|
|
15
|
+
ApplicationPackageEntityModel,
|
|
16
|
+
)
|
|
8
17
|
from snowflake.cli._plugins.nativeapp.common_flags import (
|
|
9
18
|
ForceOption,
|
|
10
19
|
InteractiveOption,
|
|
@@ -15,9 +24,7 @@ from snowflake.cli._plugins.nativeapp.constants import (
|
|
|
15
24
|
COMMENT_COL,
|
|
16
25
|
NAME_COL,
|
|
17
26
|
OWNER_COL,
|
|
18
|
-
PATCH_COL,
|
|
19
27
|
SPECIAL_COMMENT,
|
|
20
|
-
VERSION_COL,
|
|
21
28
|
)
|
|
22
29
|
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
23
30
|
ApplicationPackageDoesNotExistError,
|
|
@@ -36,13 +43,9 @@ from snowflake.cli._plugins.nativeapp.utils import (
|
|
|
36
43
|
)
|
|
37
44
|
from snowflake.cli._plugins.workspace.action_context import ActionContext
|
|
38
45
|
from snowflake.cli.api.console.abc import AbstractConsole
|
|
39
|
-
from snowflake.cli.api.entities.application_package_entity import (
|
|
40
|
-
ApplicationPackageEntity,
|
|
41
|
-
)
|
|
42
46
|
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
43
47
|
from snowflake.cli.api.entities.utils import (
|
|
44
48
|
drop_generic_object,
|
|
45
|
-
ensure_correct_owner,
|
|
46
49
|
execute_post_deploy_hooks,
|
|
47
50
|
generic_sql_error_handler,
|
|
48
51
|
print_messages,
|
|
@@ -54,22 +57,11 @@ from snowflake.cli.api.errno import (
|
|
|
54
57
|
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
55
58
|
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
56
59
|
)
|
|
57
|
-
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
58
|
-
from snowflake.cli.api.project.schemas.entities.application_entity_model import (
|
|
59
|
-
ApplicationEntityModel,
|
|
60
|
-
)
|
|
61
|
-
from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
|
|
62
|
-
ApplicationPackageEntityModel,
|
|
63
|
-
)
|
|
64
60
|
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
65
61
|
from snowflake.cli.api.project.util import (
|
|
66
62
|
extract_schema,
|
|
67
|
-
identifier_to_show_like_pattern,
|
|
68
|
-
unquote_identifier,
|
|
69
63
|
)
|
|
70
|
-
from snowflake.cli.api.utils.cursor import find_all_rows
|
|
71
64
|
from snowflake.connector import ProgrammingError
|
|
72
|
-
from snowflake.connector.cursor import DictCursor
|
|
73
65
|
|
|
74
66
|
# Reasons why an `alter application ... upgrade` might fail
|
|
75
67
|
UPGRADE_RESTRICTION_CODES = {
|
|
@@ -108,9 +100,9 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
108
100
|
app_name = model.fqn.identifier
|
|
109
101
|
debug_mode = model.debug
|
|
110
102
|
if model.meta:
|
|
111
|
-
app_role =
|
|
112
|
-
app_warehouse =
|
|
113
|
-
post_deploy_hooks =
|
|
103
|
+
app_role = model.meta.role or ctx.default_role
|
|
104
|
+
app_warehouse = model.meta.warehouse or ctx.default_warehouse
|
|
105
|
+
post_deploy_hooks = model.meta.post_deploy
|
|
114
106
|
else:
|
|
115
107
|
app_role = ctx.default_role
|
|
116
108
|
app_warehouse = ctx.default_warehouse
|
|
@@ -147,6 +139,8 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
147
139
|
paths=[],
|
|
148
140
|
validate=validate,
|
|
149
141
|
stage_fqn=stage_fqn,
|
|
142
|
+
interactive=interactive,
|
|
143
|
+
force=force,
|
|
150
144
|
)
|
|
151
145
|
|
|
152
146
|
self.deploy(
|
|
@@ -221,10 +215,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
221
215
|
)
|
|
222
216
|
return
|
|
223
217
|
|
|
224
|
-
# 2. Check
|
|
225
|
-
ensure_correct_owner(row=show_obj_row, role=app_role, obj_name=app_name)
|
|
226
|
-
|
|
227
|
-
# 3. Check if created by the Snowflake CLI
|
|
218
|
+
# 2. Check if created by the Snowflake CLI
|
|
228
219
|
row_comment = show_obj_row[COMMENT_COL]
|
|
229
220
|
if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
|
|
230
221
|
needs_confirm, auto_yes
|
|
@@ -251,7 +242,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
251
242
|
# leave behind an orphan app when we get to dropping the package
|
|
252
243
|
raise typer.Abort()
|
|
253
244
|
|
|
254
|
-
#
|
|
245
|
+
# 3. Check for application objects owned by the application
|
|
255
246
|
# This query will fail if the application package has already been dropped, so handle this case gracefully
|
|
256
247
|
has_objects_to_drop = False
|
|
257
248
|
message_prefix = ""
|
|
@@ -329,7 +320,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
329
320
|
# If there's nothing to drop, set cascade to an explicit False value
|
|
330
321
|
cascade = False
|
|
331
322
|
|
|
332
|
-
#
|
|
323
|
+
# 4. All validations have passed, drop object
|
|
333
324
|
drop_generic_object(
|
|
334
325
|
console=console,
|
|
335
326
|
object_type="application",
|
|
@@ -425,7 +416,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
425
416
|
# versioned dev
|
|
426
417
|
if version:
|
|
427
418
|
try:
|
|
428
|
-
version_exists =
|
|
419
|
+
version_exists = ApplicationPackageEntity.get_existing_version_info(
|
|
429
420
|
version=version,
|
|
430
421
|
package_name=package_name,
|
|
431
422
|
package_role=package_role,
|
|
@@ -658,40 +649,3 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
658
649
|
return sql_executor.show_specific_object(
|
|
659
650
|
"applications", app_name, name_col=NAME_COL
|
|
660
651
|
)
|
|
661
|
-
|
|
662
|
-
@staticmethod
|
|
663
|
-
def get_existing_version_info(
|
|
664
|
-
version: str,
|
|
665
|
-
package_name: str,
|
|
666
|
-
package_role: str,
|
|
667
|
-
) -> Optional[dict]:
|
|
668
|
-
"""
|
|
669
|
-
Get the latest patch on an existing version by name in the application package.
|
|
670
|
-
Executes 'show versions like ... in application package' query and returns
|
|
671
|
-
the latest patch in the version as a single row, if one exists. Otherwise,
|
|
672
|
-
returns None.
|
|
673
|
-
"""
|
|
674
|
-
sql_executor = get_sql_executor()
|
|
675
|
-
with sql_executor.use_role(package_role):
|
|
676
|
-
try:
|
|
677
|
-
query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
|
|
678
|
-
cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
|
|
679
|
-
|
|
680
|
-
if cursor.rowcount is None:
|
|
681
|
-
raise SnowflakeSQLExecutionError(query)
|
|
682
|
-
|
|
683
|
-
matching_rows = find_all_rows(
|
|
684
|
-
cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
if not matching_rows:
|
|
688
|
-
return None
|
|
689
|
-
|
|
690
|
-
return max(matching_rows, key=lambda row: row[PATCH_COL])
|
|
691
|
-
|
|
692
|
-
except ProgrammingError as err:
|
|
693
|
-
if err.msg.__contains__("does not exist or not authorized"):
|
|
694
|
-
raise ApplicationPackageDoesNotExistError(package_name)
|
|
695
|
-
else:
|
|
696
|
-
generic_sql_error_handler(err=err, role=package_role)
|
|
697
|
-
return None
|
snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_entity_model.py
RENAMED
|
@@ -17,14 +17,14 @@ from __future__ import annotations
|
|
|
17
17
|
from typing import Literal, Optional
|
|
18
18
|
|
|
19
19
|
from pydantic import Field, field_validator
|
|
20
|
-
from snowflake.cli.
|
|
20
|
+
from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
|
|
21
21
|
ApplicationPackageEntityModel,
|
|
22
22
|
)
|
|
23
23
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
24
24
|
EntityModelBase,
|
|
25
|
+
Identifier,
|
|
25
26
|
TargetField,
|
|
26
27
|
)
|
|
27
|
-
from snowflake.cli.api.project.schemas.identifier_model import Identifier
|
|
28
28
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
29
29
|
DiscriminatorField,
|
|
30
30
|
)
|