snowflake-cli 3.4.1__py3-none-any.whl → 3.6.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 (73) hide show
  1. snowflake/cli/__about__.py +13 -1
  2. snowflake/cli/_app/cli_app.py +1 -10
  3. snowflake/cli/_app/commands_registration/builtin_plugins.py +7 -1
  4. snowflake/cli/_app/commands_registration/command_plugins_loader.py +3 -1
  5. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +3 -3
  6. snowflake/cli/_app/printing.py +2 -2
  7. snowflake/cli/_app/snow_connector.py +5 -4
  8. snowflake/cli/_app/telemetry.py +3 -15
  9. snowflake/cli/_app/version_check.py +4 -4
  10. snowflake/cli/_plugins/auth/__init__.py +11 -0
  11. snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
  12. snowflake/cli/_plugins/auth/keypair/commands.py +151 -0
  13. snowflake/cli/_plugins/auth/keypair/manager.py +331 -0
  14. snowflake/cli/_plugins/auth/keypair/plugin_spec.py +30 -0
  15. snowflake/cli/_plugins/connection/commands.py +79 -5
  16. snowflake/cli/_plugins/helpers/commands.py +3 -4
  17. snowflake/cli/_plugins/nativeapp/entities/application.py +4 -1
  18. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +33 -6
  19. snowflake/cli/_plugins/notebook/commands.py +3 -4
  20. snowflake/cli/_plugins/object/command_aliases.py +3 -1
  21. snowflake/cli/_plugins/object/manager.py +4 -2
  22. snowflake/cli/_plugins/plugin/commands.py +79 -0
  23. snowflake/cli/_plugins/plugin/manager.py +74 -0
  24. snowflake/cli/_plugins/plugin/plugin_spec.py +30 -0
  25. snowflake/cli/_plugins/project/__init__.py +0 -0
  26. snowflake/cli/_plugins/project/commands.py +173 -0
  27. snowflake/cli/{_app/api_impl/plugin/__init__.py → _plugins/project/feature_flags.py} +9 -0
  28. snowflake/cli/_plugins/project/manager.py +76 -0
  29. snowflake/cli/_plugins/project/plugin_spec.py +30 -0
  30. snowflake/cli/_plugins/project/project_entity_model.py +40 -0
  31. snowflake/cli/_plugins/snowpark/commands.py +2 -1
  32. snowflake/cli/_plugins/spcs/compute_pool/commands.py +70 -10
  33. snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity.py +8 -0
  34. snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity_model.py +37 -0
  35. snowflake/cli/_plugins/spcs/compute_pool/manager.py +45 -0
  36. snowflake/cli/_plugins/spcs/image_repository/commands.py +29 -0
  37. snowflake/cli/_plugins/spcs/image_repository/image_repository_entity.py +8 -0
  38. snowflake/cli/_plugins/spcs/image_repository/image_repository_entity_model.py +8 -0
  39. snowflake/cli/_plugins/spcs/image_repository/manager.py +1 -1
  40. snowflake/cli/_plugins/spcs/services/commands.py +53 -0
  41. snowflake/cli/_plugins/spcs/services/manager.py +114 -0
  42. snowflake/cli/_plugins/spcs/services/service_entity.py +6 -0
  43. snowflake/cli/_plugins/spcs/services/service_entity_model.py +45 -0
  44. snowflake/cli/_plugins/spcs/services/service_project_paths.py +15 -0
  45. snowflake/cli/_plugins/sql/manager.py +42 -51
  46. snowflake/cli/_plugins/sql/source_reader.py +230 -0
  47. snowflake/cli/_plugins/stage/manager.py +10 -4
  48. snowflake/cli/_plugins/streamlit/commands.py +9 -24
  49. snowflake/cli/_plugins/streamlit/manager.py +5 -36
  50. snowflake/cli/api/artifacts/upload.py +51 -0
  51. snowflake/cli/api/commands/flags.py +35 -10
  52. snowflake/cli/api/commands/snow_typer.py +12 -0
  53. snowflake/cli/api/commands/utils.py +2 -0
  54. snowflake/cli/api/config.py +15 -10
  55. snowflake/cli/api/constants.py +2 -0
  56. snowflake/cli/api/errno.py +1 -0
  57. snowflake/cli/api/exceptions.py +15 -1
  58. snowflake/cli/api/feature_flags.py +2 -0
  59. snowflake/cli/api/plugins/plugin_config.py +43 -4
  60. snowflake/cli/api/project/definition_helper.py +31 -0
  61. snowflake/cli/api/project/schemas/entities/entities.py +26 -0
  62. snowflake/cli/api/rest_api.py +2 -3
  63. snowflake/cli/{_app → api}/secret.py +4 -1
  64. snowflake/cli/api/secure_path.py +16 -4
  65. snowflake/cli/api/sql_execution.py +7 -3
  66. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/METADATA +12 -12
  67. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/RECORD +71 -50
  68. snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
  69. snowflake/cli/api/__init__.py +0 -48
  70. /snowflake/cli/{_app/api_impl → _plugins/plugin}/__init__.py +0 -0
  71. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/WHEEL +0 -0
  72. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/entry_points.txt +0 -0
  73. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,331 @@
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)
@@ -0,0 +1,30 @@
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 snowflake.cli._plugins.auth import app
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=app.create_instance(),
30
+ )
@@ -16,8 +16,9 @@ from __future__ import annotations
16
16
 
17
17
  import logging
18
18
  import os.path
19
+ from copy import deepcopy
19
20
  from pathlib import Path
20
- from typing import Optional
21
+ from typing import Dict, Optional, Tuple
21
22
 
22
23
  import typer
23
24
  from click import ( # type: ignore
@@ -28,10 +29,14 @@ from click import ( # type: ignore
28
29
  )
29
30
  from click.core import ParameterSource # type: ignore
30
31
  from snowflake import connector
32
+ from snowflake.cli._app.snow_connector import connect_to_snowflake
33
+ from snowflake.cli._plugins.auth.keypair.commands import KEY_PAIR_DEFAULT_PATH
34
+ from snowflake.cli._plugins.auth.keypair.manager import AuthManager
31
35
  from snowflake.cli._plugins.connection.util import (
32
36
  strip_if_value_present,
33
37
  )
34
38
  from snowflake.cli._plugins.object.manager import ObjectManager
39
+ from snowflake.cli.api import exceptions
35
40
  from snowflake.cli.api.cli_global_context import get_cli_context
36
41
  from snowflake.cli.api.commands.flags import (
37
42
  PLAIN_PASSWORD_MSG,
@@ -67,6 +72,8 @@ from snowflake.cli.api.output.types import (
67
72
  MessageResult,
68
73
  ObjectResult,
69
74
  )
75
+ from snowflake.cli.api.secret import SecretType
76
+ from snowflake.cli.api.secure_path import SecurePath
70
77
  from snowflake.connector import ProgrammingError
71
78
  from snowflake.connector.constants import CONNECTIONS_FILE
72
79
 
@@ -282,15 +289,26 @@ def add(
282
289
  if connection_exists(connection_name):
283
290
  raise UsageError(f"Connection {connection_name} already exists")
284
291
 
292
+ if not no_interactive:
293
+ connection_options, keypair_error = _extend_add_with_key_pair(
294
+ connection_name, connection_options
295
+ )
296
+ else:
297
+ keypair_error = ""
298
+
285
299
  connections_file = add_connection_to_proper_file(
286
300
  connection_name,
287
301
  ConnectionConfig(**connection_options),
288
302
  )
289
303
  if set_as_default:
290
- set_config_value(
291
- section=None, key="default_connection_name", value=connection_name
292
- )
304
+ set_config_value(path=["default_connection_name"], value=connection_name)
293
305
 
306
+ if keypair_error:
307
+ return MessageResult(
308
+ f"Wrote new password-based connection {connection_name} to {connections_file}, "
309
+ f"however there were some issues during key pair setup. Review the following error and check 'snow auth keypair' "
310
+ f"commands to setup key pair authentication:\n * {keypair_error}"
311
+ )
294
312
  return MessageResult(
295
313
  f"Wrote new connection {connection_name} to {connections_file}"
296
314
  )
@@ -357,7 +375,7 @@ def set_default(
357
375
  ):
358
376
  """Changes default connection to provided value."""
359
377
  get_connection_dict(connection_name=name)
360
- set_config_value(section=None, key="default_connection_name", value=name)
378
+ set_config_value(path=["default_connection_name"], value=name)
361
379
  return MessageResult(f"Default connection set to: {name}")
362
380
 
363
381
 
@@ -404,3 +422,59 @@ def generate_jwt(
404
422
  return MessageResult(token)
405
423
  except (ValueError, TypeError) as err:
406
424
  raise ClickException(str(err))
425
+
426
+
427
+ def _extend_add_with_key_pair(
428
+ connection_name: str, connection_options: Dict
429
+ ) -> Tuple[Dict, str]:
430
+ if not _should_extend_with_key_pair(connection_options):
431
+ return connection_options, ""
432
+
433
+ configure_key_pair = typer.confirm(
434
+ "Do you want to configure key pair authentication?",
435
+ default=False,
436
+ )
437
+ if not configure_key_pair:
438
+ return connection_options, ""
439
+
440
+ key_length = typer.prompt(
441
+ "Key length",
442
+ default=2048,
443
+ show_default=True,
444
+ )
445
+
446
+ output_path = typer.prompt(
447
+ "Output path",
448
+ default=KEY_PAIR_DEFAULT_PATH,
449
+ show_default=True,
450
+ value_proc=lambda value: SecurePath(value),
451
+ )
452
+ private_key_passphrase = typer.prompt(
453
+ "Private key passphrase",
454
+ default="",
455
+ hide_input=True,
456
+ show_default=False,
457
+ value_proc=lambda value: SecretType(value),
458
+ )
459
+ connection = connect_to_snowflake(temporary_connection=True, **connection_options)
460
+ try:
461
+ connection_options = AuthManager(connection=connection).extend_connection_add(
462
+ connection_name=connection_name,
463
+ connection_options=deepcopy(connection_options),
464
+ key_length=key_length,
465
+ output_path=output_path,
466
+ private_key_passphrase=private_key_passphrase,
467
+ )
468
+ except exceptions.CouldNotSetKeyPairError:
469
+ return connection_options, "The public key is set already."
470
+ except Exception as e:
471
+ return connection_options, str(e)
472
+ return connection_options, ""
473
+
474
+
475
+ def _should_extend_with_key_pair(connection_options: Dict) -> bool:
476
+ return (
477
+ connection_options.get("password") is not None
478
+ and connection_options.get("private_key_file") is None
479
+ and connection_options.get("private_key_path") is None
480
+ )
@@ -49,7 +49,7 @@ def v1_to_v2(
49
49
  accept_templates: bool = typer.Option(
50
50
  False, "-t", "--accept-templates", help="Allows the migration of templates."
51
51
  ),
52
- migrate_local_yml: (bool | None) = typer.Option(
52
+ migrate_local_yml: Optional[bool] = typer.Option(
53
53
  None,
54
54
  "-l",
55
55
  "--migrate-local-overrides/--no-migrate-local-overrides",
@@ -234,7 +234,7 @@ def _validate_imported_default_connection_name(
234
234
 
235
235
 
236
236
  def _convert_connection_from_snowsql_config_section(
237
- snowsql_connection: list[tuple[str, Any]]
237
+ snowsql_connection: list[tuple[str, Any]],
238
238
  ) -> dict[str, Any]:
239
239
  from ast import literal_eval
240
240
 
@@ -290,7 +290,6 @@ def _validate_and_save_connections_imported_from_snowsql(
290
290
  f"Setting [{default_cli_connection_name}] connection as Snowflake CLI's default connection."
291
291
  )
292
292
  set_config_value(
293
- section=None,
294
- key="default_connection_name",
293
+ path=["default_connection_name"],
295
294
  value=default_cli_connection_name,
296
295
  )
@@ -669,7 +669,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
669
669
  role_to_use=package.role,
670
670
  )
671
671
 
672
- return get_snowflake_facade().create_application(
672
+ create_app_result, warnings = get_snowflake_facade().create_application(
673
673
  name=self.name,
674
674
  package_name=package.name,
675
675
  install_method=install_method,
@@ -680,6 +680,9 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
680
680
  warehouse=self.warehouse,
681
681
  release_channel=release_channel,
682
682
  )
683
+ for warning in warnings:
684
+ self.console.warning(warning)
685
+ return create_app_result
683
686
 
684
687
  @span("update_app_object")
685
688
  def create_or_upgrade_app(
@@ -60,6 +60,7 @@ from snowflake.cli.api.errno import (
60
60
  CANNOT_DISABLE_MANDATORY_TELEMETRY,
61
61
  CANNOT_DISABLE_RELEASE_CHANNELS,
62
62
  CANNOT_MODIFY_RELEASE_CHANNEL_ACCOUNTS,
63
+ CANNOT_SET_DEBUG_MODE_WITH_MANIFEST_VERSION,
63
64
  DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
64
65
  DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
65
66
  INSUFFICIENT_PRIVILEGES,
@@ -854,7 +855,7 @@ class SnowflakeSQLFacade:
854
855
  debug_mode: bool | None,
855
856
  should_authorize_event_sharing: bool | None,
856
857
  release_channel: str | None = None,
857
- ) -> list[tuple[str]]:
858
+ ) -> tuple[list[tuple[str]], list[str]]:
858
859
  """
859
860
  Creates a new application object using an application package,
860
861
  running the setup script of the application package
@@ -868,6 +869,7 @@ class SnowflakeSQLFacade:
868
869
  @param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
869
870
  @param should_authorize_event_sharing: Whether to enable event sharing; None means not explicitly enabled or disabled
870
871
  @param release_channel [Optional]: Release channel to use when creating the application
872
+ @return: a tuple containing the result of the create application query and possible warning messages
871
873
  """
872
874
  package_name = to_identifier(package_name)
873
875
  name = to_identifier(name)
@@ -875,11 +877,9 @@ class SnowflakeSQLFacade:
875
877
 
876
878
  # by default, applications are created in debug mode when possible;
877
879
  # this can be overridden in the project definition
878
- debug_mode_clause = ""
880
+ initial_debug_mode = False
879
881
  if install_method.is_dev_mode:
880
882
  initial_debug_mode = debug_mode if debug_mode is not None else True
881
- debug_mode_clause = f"debug_mode = {initial_debug_mode}"
882
-
883
883
  authorize_telemetry_clause = ""
884
884
  if should_authorize_event_sharing is not None:
885
885
  self._log.info(
@@ -903,13 +903,13 @@ class SnowflakeSQLFacade:
903
903
  from application package {package_name}
904
904
  {using_clause}
905
905
  {release_channel_clause}
906
- {debug_mode_clause}
907
906
  {authorize_telemetry_clause}
908
907
  comment = {SPECIAL_COMMENT}
909
908
  """
910
909
  )
911
910
  ),
912
911
  )
912
+
913
913
  except Exception as err:
914
914
  if isinstance(err, ProgrammingError):
915
915
  if err.errno == APPLICATION_REQUIRES_TELEMETRY_SHARING:
@@ -927,9 +927,36 @@ class SnowflakeSQLFacade:
927
927
  f"Failed to create application {name} with the following error message:\n"
928
928
  f"{err.msg}"
929
929
  ) from err
930
+
930
931
  handle_unclassified_error(err, f"Failed to create application {name}.")
931
932
 
932
- return create_cursor.fetchall()
933
+ warnings = []
934
+ try:
935
+ if initial_debug_mode:
936
+ self._sql_executor.execute_query(
937
+ dedent(
938
+ _strip_empty_lines(
939
+ f"""\
940
+ alter application {name}
941
+ set debug_mode = {initial_debug_mode}
942
+ """
943
+ )
944
+ )
945
+ )
946
+ except Exception as err:
947
+ if (
948
+ isinstance(err, ProgrammingError)
949
+ and err.errno == CANNOT_SET_DEBUG_MODE_WITH_MANIFEST_VERSION
950
+ ):
951
+ warnings.append(
952
+ "Did not apply debug mode to application because the manifest version is set to 2 or higher. Please use session debugging instead."
953
+ )
954
+ else:
955
+ warnings.append(
956
+ f"Failed to set debug mode for application {name}. {str(err)}"
957
+ )
958
+
959
+ return create_cursor.fetchall(), warnings
933
960
 
934
961
  def create_application_package(
935
962
  self,
@@ -21,9 +21,7 @@ from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntity
21
21
  from snowflake.cli._plugins.notebook.types import NotebookStagePath
22
22
  from snowflake.cli._plugins.workspace.manager import WorkspaceManager
23
23
  from snowflake.cli.api.cli_global_context import get_cli_context
24
- from snowflake.cli.api.commands.decorators import (
25
- with_project_definition,
26
- )
24
+ from snowflake.cli.api.commands.decorators import with_project_definition
27
25
  from snowflake.cli.api.commands.flags import (
28
26
  ReplaceOption,
29
27
  entity_argument,
@@ -107,7 +105,8 @@ def create(
107
105
  def deploy(
108
106
  entity_id: str = entity_argument("notebook"),
109
107
  replace: bool = ReplaceOption(
110
- help="Replace notebook object if it already exists.",
108
+ help="Replace notebook object if it already exists. It only uploads new and overwrites existing files, "
109
+ "but does not remove any files already on the stage.",
111
110
  ),
112
111
  **options,
113
112
  ) -> CommandResult:
@@ -36,8 +36,10 @@ def add_object_command_aliases(
36
36
  name_argument: typer.Argument,
37
37
  like_option: Optional[typer.Option],
38
38
  scope_option: Optional[typer.Option],
39
- ommit_commands: List[str] = [],
39
+ ommit_commands: Optional[List[str]] = None,
40
40
  ):
41
+ if ommit_commands is None:
42
+ ommit_commands = list()
41
43
  if "list" not in ommit_commands:
42
44
  if not like_option:
43
45
  raise ClickException('[like_option] have to be defined for "list" command')
@@ -58,14 +58,16 @@ class ObjectManager(SqlExecutionMixin):
58
58
  object_name = _get_object_names(object_type).sf_name
59
59
  return self.execute_query(f"drop {object_name} {fqn.sql_identifier}")
60
60
 
61
- def describe(self, *, object_type: str, fqn: FQN):
61
+ def describe(self, *, object_type: str, fqn: FQN, **kwargs):
62
62
  # Image repository is the only supported object that does not have a DESCRIBE command.
63
63
  if object_type == "image-repository":
64
64
  raise ClickException(
65
65
  f"Describe is currently not supported for object of type image-repository"
66
66
  )
67
67
  object_name = _get_object_names(object_type).sf_name
68
- return self.execute_query(f"describe {object_name} {fqn.sql_identifier}")
68
+ return self.execute_query(
69
+ f"describe {object_name} {fqn.sql_identifier}", **kwargs
70
+ )
69
71
 
70
72
  def object_exists(self, *, object_type: str, fqn: FQN):
71
73
  try: