snowflake-cli 3.11.0__py3-none-any.whl → 3.13.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.
Files changed (56) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +43 -1
  3. snowflake/cli/_app/commands_registration/builtin_plugins.py +1 -1
  4. snowflake/cli/_app/commands_registration/command_plugins_loader.py +14 -1
  5. snowflake/cli/_app/printing.py +153 -19
  6. snowflake/cli/_app/telemetry.py +25 -10
  7. snowflake/cli/_plugins/auth/__init__.py +0 -2
  8. snowflake/cli/_plugins/connection/commands.py +1 -78
  9. snowflake/cli/_plugins/dbt/commands.py +44 -19
  10. snowflake/cli/_plugins/dbt/constants.py +1 -1
  11. snowflake/cli/_plugins/dbt/manager.py +252 -47
  12. snowflake/cli/_plugins/dcm/commands.py +65 -90
  13. snowflake/cli/_plugins/dcm/manager.py +137 -50
  14. snowflake/cli/_plugins/logs/commands.py +7 -0
  15. snowflake/cli/_plugins/logs/manager.py +21 -1
  16. snowflake/cli/_plugins/nativeapp/entities/application_package.py +4 -1
  17. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +3 -1
  18. snowflake/cli/_plugins/object/manager.py +1 -0
  19. snowflake/cli/_plugins/snowpark/common.py +1 -0
  20. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +29 -5
  21. snowflake/cli/_plugins/snowpark/package_utils.py +44 -3
  22. snowflake/cli/_plugins/spcs/services/commands.py +19 -1
  23. snowflake/cli/_plugins/spcs/services/manager.py +17 -4
  24. snowflake/cli/_plugins/spcs/services/service_entity_model.py +5 -0
  25. snowflake/cli/_plugins/sql/lexer/types.py +1 -0
  26. snowflake/cli/_plugins/sql/repl.py +100 -26
  27. snowflake/cli/_plugins/sql/repl_commands.py +607 -0
  28. snowflake/cli/_plugins/sql/statement_reader.py +44 -20
  29. snowflake/cli/_plugins/streamlit/streamlit_entity.py +28 -2
  30. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +24 -4
  31. snowflake/cli/api/artifacts/bundle_map.py +32 -2
  32. snowflake/cli/api/artifacts/regex_resolver.py +54 -0
  33. snowflake/cli/api/artifacts/upload.py +5 -1
  34. snowflake/cli/api/artifacts/utils.py +12 -1
  35. snowflake/cli/api/cli_global_context.py +7 -0
  36. snowflake/cli/api/commands/decorators.py +7 -0
  37. snowflake/cli/api/commands/flags.py +24 -1
  38. snowflake/cli/api/console/abc.py +13 -2
  39. snowflake/cli/api/console/console.py +20 -0
  40. snowflake/cli/api/constants.py +9 -0
  41. snowflake/cli/api/entities/utils.py +10 -6
  42. snowflake/cli/api/feature_flags.py +3 -2
  43. snowflake/cli/api/identifiers.py +18 -1
  44. snowflake/cli/api/project/schemas/entities/entities.py +0 -6
  45. snowflake/cli/api/rendering/sql_templates.py +2 -0
  46. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/METADATA +7 -7
  47. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/RECORD +51 -54
  48. snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
  49. snowflake/cli/_plugins/auth/keypair/commands.py +0 -153
  50. snowflake/cli/_plugins/auth/keypair/manager.py +0 -331
  51. snowflake/cli/_plugins/dcm/dcm_project_entity_model.py +0 -59
  52. snowflake/cli/_plugins/sql/snowsql_commands.py +0 -331
  53. /snowflake/cli/_plugins/auth/{keypair/plugin_spec.py → plugin_spec.py} +0 -0
  54. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/WHEEL +0 -0
  55. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/entry_points.txt +0 -0
  56. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,331 +0,0 @@
1
- from enum import Enum
2
- from typing import Dict, List, Optional, Tuple
3
-
4
- from click import ClickException
5
- from cryptography.hazmat.primitives import serialization
6
- from cryptography.hazmat.primitives.asymmetric import rsa
7
- from snowflake.cli._plugins.object.manager import ObjectManager
8
- from snowflake.cli.api import exceptions
9
- from snowflake.cli.api.cli_global_context import (
10
- _CliGlobalContextAccess,
11
- get_cli_context,
12
- )
13
- from snowflake.cli.api.config import (
14
- connection_exists,
15
- get_connection_dict,
16
- set_config_value,
17
- )
18
- from snowflake.cli.api.console import cli_console
19
- from snowflake.cli.api.constants import ObjectType
20
- from snowflake.cli.api.identifiers import FQN
21
- from snowflake.cli.api.secret import SecretType
22
- from snowflake.cli.api.secure_path import SecurePath
23
- from snowflake.cli.api.sql_execution import SqlExecutionMixin
24
- from snowflake.connector import DictCursor
25
- from snowflake.connector.cursor import SnowflakeCursor
26
-
27
-
28
- class PublicKeyProperty(Enum):
29
- RSA_PUBLIC_KEY = "RSA_PUBLIC_KEY"
30
- RSA_PUBLIC_KEY_2 = "RSA_PUBLIC_KEY_2"
31
-
32
-
33
- class AuthManager(SqlExecutionMixin):
34
- def setup(
35
- self,
36
- connection_name: Optional[str],
37
- key_length: int,
38
- output_path: SecurePath,
39
- private_key_passphrase: SecretType,
40
- ):
41
- # When the user provide new connection name
42
- if connection_name and connection_exists(connection_name):
43
- raise ClickException(
44
- f"Connection with name {connection_name} already exists."
45
- )
46
-
47
- cli_context = get_cli_context()
48
- # When the use not provide connection name, so we overwrite the current connection
49
- if not connection_name:
50
- connection_name = cli_context.connection_context.connection_name
51
-
52
- key_name = AuthManager._get_free_key_name(output_path, connection_name) # type: ignore[arg-type]
53
- self._generate_key_pair_and_set_public_key(
54
- user=cli_context.connection.user,
55
- key_length=key_length,
56
- output_path=output_path,
57
- key_name=key_name, # type: ignore[arg-type]
58
- private_key_passphrase=private_key_passphrase,
59
- )
60
-
61
- self._create_or_update_connection(
62
- current_connection=cli_context.connection_context.connection_name,
63
- connection_name=connection_name, # type: ignore[arg-type]
64
- private_key_path=self._get_private_key_path(
65
- output_path=output_path, key_name=key_name # type: ignore[arg-type]
66
- ),
67
- )
68
-
69
- def rotate(
70
- self,
71
- key_length: int,
72
- output_path: SecurePath,
73
- private_key_passphrase: SecretType,
74
- ):
75
- cli_context = get_cli_context()
76
- connection_name = cli_context.connection_context.connection_name
77
-
78
- self._ensure_connection_has_private_key(
79
- cli_context.connection_context.connection_name
80
- )
81
-
82
- public_key, public_key_2 = self._get_public_keys()
83
-
84
- if not public_key and not public_key_2:
85
- raise ClickException("No public key found. Use the setup command first.")
86
-
87
- if public_key_2:
88
- self.set_public_key(
89
- cli_context.connection.user,
90
- PublicKeyProperty.RSA_PUBLIC_KEY,
91
- public_key_2,
92
- )
93
-
94
- key_name = AuthManager._get_free_key_name(output_path, connection_name)
95
- public_key = self._generate_keys_and_return_public_key(
96
- key_length=key_length,
97
- output_path=output_path,
98
- key_name=key_name,
99
- private_key_passphrase=private_key_passphrase,
100
- )
101
- self.set_public_key(
102
- cli_context.connection.user, PublicKeyProperty.RSA_PUBLIC_KEY_2, public_key
103
- )
104
- self._create_or_update_connection(
105
- current_connection=cli_context.connection_context.connection_name,
106
- connection_name=connection_name,
107
- private_key_path=self._get_private_key_path(
108
- output_path=output_path, key_name=key_name
109
- ),
110
- )
111
-
112
- def _generate_key_pair_and_set_public_key(
113
- self,
114
- user: str,
115
- key_length: int,
116
- output_path: SecurePath,
117
- key_name: str,
118
- private_key_passphrase: SecretType,
119
- ):
120
- public_key_exists, public_key_2_exists = self._get_public_keys()
121
-
122
- if public_key_exists or public_key_2_exists:
123
- raise exceptions.CouldNotSetKeyPairError()
124
-
125
- if not output_path.exists():
126
- output_path.mkdir(parents=True)
127
-
128
- public_key = self._generate_keys_and_return_public_key(
129
- key_length=key_length,
130
- output_path=output_path,
131
- key_name=key_name, # type: ignore[arg-type]
132
- private_key_passphrase=private_key_passphrase,
133
- )
134
- self.set_public_key(user, PublicKeyProperty.RSA_PUBLIC_KEY, public_key)
135
-
136
- def list_keys(self) -> List[Dict]:
137
- key_properties = [
138
- "RSA_PUBLIC_KEY",
139
- "RSA_PUBLIC_KEY_FP",
140
- "RSA_PUBLIC_KEY_LAST_SET_TIME",
141
- "RSA_PUBLIC_KEY_2",
142
- "RSA_PUBLIC_KEY_2_FP",
143
- "RSA_PUBLIC_KEY_2_LAST_SET_TIME",
144
- ]
145
- cursor = ObjectManager(connection=self._conn).describe(
146
- object_type=ObjectType.USER.value.sf_name,
147
- fqn=FQN.from_string(self._conn.user),
148
- cursor_class=DictCursor,
149
- )
150
- only_public_key_properties = [
151
- p for p in cursor.fetchall() if p.get("property") in key_properties
152
- ]
153
- return only_public_key_properties
154
-
155
- def set_public_key(
156
- self, user: str, public_key_property: PublicKeyProperty, public_key: str
157
- ) -> SnowflakeCursor:
158
- return self.execute_query(
159
- f"ALTER USER {user} SET {public_key_property.value}='{public_key}'"
160
- )
161
-
162
- def remove_public_key(
163
- self, public_key_property: PublicKeyProperty
164
- ) -> SnowflakeCursor:
165
- cli_context = get_cli_context()
166
- return self.execute_query(
167
- f"ALTER USER {cli_context.connection.user} UNSET {public_key_property.value}"
168
- )
169
-
170
- def status(self):
171
- cli_context = get_cli_context()
172
- self._ensure_connection_has_private_key(
173
- cli_context.connection_context.connection_name
174
- )
175
- cli_console.step("Private key set for connection - OK")
176
- self._check_connection(cli_context)
177
- cli_console.step("Test connection - OK")
178
-
179
- def extend_connection_add(
180
- self,
181
- connection_name: str,
182
- connection_options: Dict,
183
- key_length: int,
184
- output_path: SecurePath,
185
- private_key_passphrase: SecretType,
186
- ) -> Dict:
187
- key_name = AuthManager._get_free_key_name(output_path, connection_name)
188
-
189
- self._generate_key_pair_and_set_public_key(
190
- user=connection_options["user"],
191
- key_length=key_length,
192
- output_path=output_path,
193
- key_name=key_name,
194
- private_key_passphrase=private_key_passphrase,
195
- )
196
-
197
- connection_options["authenticator"] = "SNOWFLAKE_JWT"
198
- connection_options["private_key_file"] = str(
199
- self._get_private_key_path(output_path=output_path, key_name=key_name).path
200
- )
201
- if connection_options.get("password"):
202
- del connection_options["password"]
203
-
204
- return connection_options
205
-
206
- @staticmethod
207
- def _ensure_connection_has_private_key(connection_name: str) -> None:
208
- connection = get_connection_dict(connection_name)
209
- if not connection.get("private_key_file") and not connection.get(
210
- "private_key_path"
211
- ):
212
- raise ClickException(
213
- f"The private key is not set in {connection_name} connection."
214
- )
215
-
216
- @staticmethod
217
- def _check_connection(cli_context: _CliGlobalContextAccess) -> None:
218
- cli_context.connection
219
-
220
- def _get_public_keys(self) -> Tuple[str, str]:
221
- keys = self.list_keys()
222
- public_key = ""
223
- public_key_2 = ""
224
- for p in keys:
225
- if (
226
- p.get("property") == PublicKeyProperty.RSA_PUBLIC_KEY.value
227
- and p.get("value") != "null"
228
- ):
229
- public_key = p.get("value") # type: ignore
230
- if (
231
- p.get("property") == PublicKeyProperty.RSA_PUBLIC_KEY_2.value
232
- and p.get("value") != "null"
233
- ):
234
- public_key_2 = p.get("value") # type: ignore
235
- return public_key, public_key_2
236
-
237
- @staticmethod
238
- def _generate_keys_and_return_public_key(
239
- key_length: int,
240
- output_path: SecurePath,
241
- key_name: str,
242
- private_key_passphrase: SecretType,
243
- ) -> str:
244
- private_key = rsa.generate_private_key(
245
- public_exponent=65537,
246
- key_size=key_length,
247
- )
248
-
249
- if private_key_passphrase:
250
- pem = SecretType(
251
- private_key.private_bytes(
252
- encoding=serialization.Encoding.PEM,
253
- format=serialization.PrivateFormat.PKCS8,
254
- encryption_algorithm=serialization.BestAvailableEncryption(
255
- private_key_passphrase.value.encode("utf-8")
256
- ),
257
- )
258
- )
259
- cli_console.message(
260
- "Set the `PRIVATE_KEY_PASSPHRASE` environment variable before using the connection."
261
- )
262
- else:
263
- pem = SecretType(
264
- private_key.private_bytes(
265
- encoding=serialization.Encoding.PEM,
266
- format=serialization.PrivateFormat.PKCS8,
267
- encryption_algorithm=serialization.NoEncryption(),
268
- )
269
- )
270
-
271
- with AuthManager._get_private_key_path(output_path, key_name).open(
272
- mode="wb"
273
- ) as file:
274
- file.write(pem.value)
275
-
276
- public_key = private_key.public_key()
277
- public_pem = public_key.public_bytes(
278
- encoding=serialization.Encoding.PEM,
279
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
280
- )
281
- with AuthManager._get_public_key_path(output_path, key_name).open(
282
- mode="wb"
283
- ) as file:
284
- file.write(public_pem)
285
-
286
- return public_pem.decode("utf-8")
287
-
288
- @staticmethod
289
- def _get_free_key_name(output_path: SecurePath, key_name: str) -> str:
290
- new_private_key = f"{key_name}.p8"
291
- new_public_key = f"{key_name}.pub"
292
- new_key_name = key_name
293
- counter = 1
294
-
295
- while (
296
- (output_path / new_private_key).exists()
297
- and (output_path / new_public_key).exists()
298
- and counter <= 100
299
- ):
300
- new_key_name = f"{key_name}_{counter}"
301
- new_private_key = f"{new_key_name}.p8"
302
- new_public_key = f"{new_key_name}.pub"
303
- counter += 1
304
-
305
- if counter == 100:
306
- raise ClickException(
307
- "Too many key pairs with the same name in the output directory."
308
- )
309
-
310
- return new_key_name
311
-
312
- @staticmethod
313
- def _get_private_key_path(output_path: SecurePath, key_name: str) -> SecurePath:
314
- return (output_path / f"{key_name}.p8").resolve()
315
-
316
- @staticmethod
317
- def _get_public_key_path(output_path: SecurePath, key_name: str) -> SecurePath:
318
- return (output_path / f"{key_name}.pub").resolve()
319
-
320
- @staticmethod
321
- def _create_or_update_connection(
322
- current_connection: Optional[str],
323
- connection_name: str,
324
- private_key_path: SecurePath,
325
- ):
326
- connection = get_connection_dict(current_connection)
327
- connection.pop("password", None)
328
- connection["authenticator"] = "SNOWFLAKE_JWT"
329
- connection["private_key_file"] = str(private_key_path.path)
330
-
331
- set_config_value(["connections", connection_name], value=connection)
@@ -1,59 +0,0 @@
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
- from __future__ import annotations
15
-
16
- from typing import List, Literal, Optional, TypeVar
17
-
18
- from pydantic import Field, field_validator
19
- from snowflake.cli.api.cli_global_context import get_cli_context
20
- from snowflake.cli.api.entities.common import EntityBase, attach_spans_to_entity_actions
21
- from snowflake.cli.api.exceptions import CliError
22
- from snowflake.cli.api.project.schemas.entities.common import (
23
- Artifacts,
24
- EntityModelBaseWithArtifacts,
25
- PathMapping,
26
- )
27
- from snowflake.cli.api.project.schemas.updatable_model import (
28
- DiscriminatorField,
29
- )
30
- from snowflake.cli.api.secure_path import SecurePath
31
-
32
- T = TypeVar("T")
33
-
34
-
35
- MANIFEST_FILE_NAME = "manifest.yml"
36
-
37
-
38
- class DCMProjectEntityModel(EntityModelBaseWithArtifacts):
39
- type: Literal["dcm"] = DiscriminatorField() # noqa: A003
40
- stage: Optional[str] = Field(
41
- title="Stage in which the DCM Project artifacts will be stored", default=None
42
- )
43
-
44
- @field_validator("artifacts")
45
- @classmethod
46
- def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
47
- if not (
48
- SecurePath(get_cli_context().project_root) / MANIFEST_FILE_NAME
49
- ).exists():
50
- raise CliError(
51
- f"{MANIFEST_FILE_NAME} was not found in project root directory"
52
- )
53
- orig_artifacts.append(PathMapping(src=MANIFEST_FILE_NAME))
54
- return super().transform_artifacts(orig_artifacts)
55
-
56
-
57
- @attach_spans_to_entity_actions(entity_name="dcm")
58
- class DCMProjectEntity(EntityBase[DCMProjectEntityModel]):
59
- """Placeholder for project entity"""