snowflake-cli-labs 2.8.0rc1__py3-none-any.whl → 3.0.0rc1__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 → _app}/__main__.py +1 -1
- snowflake/cli/{app → _app}/cli_app.py +12 -12
- snowflake/cli/{app → _app}/commands_registration/builtin_plugins.py +13 -19
- snowflake/cli/{app → _app}/commands_registration/command_plugins_loader.py +9 -9
- snowflake/cli/{app → _app}/commands_registration/commands_registration_with_callbacks.py +4 -4
- snowflake/cli/{app → _app}/commands_registration/exception_logging.py +2 -2
- snowflake/cli/{app → _app}/commands_registration/typer_registration.py +2 -2
- snowflake/cli/{app → _app}/dev/docs/commands_docs_generator.py +30 -12
- snowflake/cli/{app → _app}/dev/docs/generator.py +3 -3
- snowflake/cli/{app → _app}/dev/docs/project_definition_docs_generator.py +4 -4
- snowflake/cli/{app → _app}/dev/docs/templates/usage.rst.jinja2 +14 -4
- snowflake/cli/{app → _app}/main_typer.py +2 -2
- snowflake/cli/{app → _app}/printing.py +2 -2
- snowflake/cli/{app → _app}/snow_connector.py +24 -17
- snowflake/cli/{app → _app}/telemetry.py +4 -5
- snowflake/cli/{plugins → _plugins}/connection/commands.py +25 -7
- snowflake/cli/_plugins/connection/plugin_spec.py +30 -0
- snowflake/cli/{plugins → _plugins}/connection/util.py +16 -0
- snowflake/cli/{plugins → _plugins}/cortex/commands.py +54 -49
- snowflake/cli/{plugins → _plugins}/cortex/constants.py +1 -1
- snowflake/cli/{plugins → _plugins}/cortex/manager.py +5 -5
- snowflake/cli/{plugins → _plugins}/cortex/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/git/commands.py +32 -20
- snowflake/cli/{plugins → _plugins}/git/manager.py +20 -11
- snowflake/cli/{plugins → _plugins}/git/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/init/commands.py +10 -6
- snowflake/cli/{plugins → _plugins}/init/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/artifacts.py +14 -0
- snowflake/cli/_plugins/nativeapp/bundle_context.py +31 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/artifact_processor.py +3 -3
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/compiler.py +32 -18
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +249 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/setup_driver.py.source +5 -2
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/extension_function_utils.py +4 -4
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/python_processor.py +23 -29
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/commands.py +171 -42
- snowflake/cli/{plugins → _plugins}/nativeapp/common_flags.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/exceptions.py +3 -3
- snowflake/cli/{plugins → _plugins}/nativeapp/init.py +1 -1
- snowflake/cli/_plugins/nativeapp/manager.py +572 -0
- snowflake/cli/{plugins/connection → _plugins/nativeapp}/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/project_model.py +35 -19
- snowflake/cli/{plugins → _plugins}/nativeapp/run_processor.py +25 -23
- snowflake/cli/{plugins → _plugins}/nativeapp/teardown_processor.py +24 -110
- snowflake/cli/{plugins → _plugins}/nativeapp/v2_conversions/v2_to_v1_decorator.py +47 -28
- snowflake/cli/{plugins → _plugins}/nativeapp/version/commands.py +15 -12
- snowflake/cli/{plugins → _plugins}/nativeapp/version/version_processor.py +22 -20
- snowflake/cli/{plugins → _plugins}/notebook/commands.py +8 -6
- snowflake/cli/{plugins → _plugins}/notebook/manager.py +14 -14
- snowflake/cli/{plugins → _plugins}/notebook/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/notebook/types.py +0 -1
- snowflake/cli/{plugins → _plugins}/object/command_aliases.py +6 -5
- snowflake/cli/{plugins → _plugins}/object/commands.py +16 -10
- snowflake/cli/{plugins → _plugins}/object/manager.py +7 -6
- snowflake/cli/{plugins → _plugins}/object/plugin_spec.py +1 -1
- snowflake/cli/_plugins/snowpark/commands.py +450 -0
- snowflake/cli/_plugins/snowpark/common.py +268 -0
- snowflake/cli/{plugins → _plugins}/snowpark/models.py +0 -7
- snowflake/cli/{plugins → _plugins}/snowpark/package/anaconda_packages.py +2 -36
- snowflake/cli/{plugins → _plugins}/snowpark/package/commands.py +13 -74
- snowflake/cli/{plugins → _plugins}/snowpark/package/manager.py +4 -3
- snowflake/cli/{plugins → _plugins}/snowpark/package_utils.py +5 -5
- snowflake/cli/{plugins/nativeapp → _plugins/snowpark}/plugin_spec.py +1 -1
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +109 -0
- snowflake/cli/{plugins → _plugins}/snowpark/snowpark_shared.py +0 -36
- snowflake/cli/{plugins → _plugins}/snowpark/zipper.py +16 -8
- snowflake/cli/{plugins → _plugins}/spcs/__init__.py +5 -7
- snowflake/cli/{plugins → _plugins}/spcs/compute_pool/commands.py +29 -28
- snowflake/cli/{plugins → _plugins}/spcs/compute_pool/manager.py +3 -3
- snowflake/cli/{plugins → _plugins}/spcs/image_registry/commands.py +3 -3
- snowflake/cli/{plugins → _plugins}/spcs/image_repository/commands.py +25 -19
- snowflake/cli/{plugins → _plugins}/spcs/image_repository/manager.py +1 -1
- snowflake/cli/{plugins → _plugins}/spcs/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/spcs/services/commands.py +66 -32
- snowflake/cli/{plugins → _plugins}/spcs/services/manager.py +43 -5
- snowflake/cli/{plugins → _plugins}/sql/commands.py +20 -17
- snowflake/cli/{plugins → _plugins}/sql/manager.py +1 -1
- snowflake/cli/{plugins → _plugins}/sql/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/stage/commands.py +20 -17
- snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
- snowflake/cli/{plugins → _plugins}/stage/manager.py +54 -21
- snowflake/cli/{plugins → _plugins}/stage/plugin_spec.py +1 -1
- snowflake/cli/_plugins/stage/utils.py +54 -0
- snowflake/cli/{plugins → _plugins}/streamlit/commands.py +59 -62
- snowflake/cli/{plugins → _plugins}/streamlit/manager.py +51 -70
- snowflake/cli/_plugins/streamlit/plugin_spec.py +30 -0
- snowflake/cli/_plugins/workspace/action_context.py +17 -0
- snowflake/cli/_plugins/workspace/commands.py +194 -0
- snowflake/cli/_plugins/workspace/manager.py +73 -0
- snowflake/cli/{plugins → _plugins}/workspace/plugin_spec.py +1 -1
- snowflake/cli/api/cli_global_context.py +40 -13
- snowflake/cli/api/commands/common.py +25 -0
- snowflake/cli/api/commands/decorators.py +5 -4
- snowflake/cli/api/commands/experimental_behaviour.py +2 -3
- snowflake/cli/api/commands/flags.py +97 -179
- snowflake/cli/api/commands/overrideable_parameter.py +143 -0
- snowflake/cli/api/commands/snow_typer.py +14 -6
- snowflake/cli/api/commands/typer_pre_execute.py +3 -3
- snowflake/cli/api/commands/utils.py +18 -0
- snowflake/cli/api/config.py +18 -5
- snowflake/cli/api/console/abc.py +5 -2
- snowflake/cli/api/constants.py +11 -0
- snowflake/cli/api/entities/application_entity.py +12 -0
- snowflake/cli/api/entities/application_package_entity.py +553 -0
- snowflake/cli/api/entities/common.py +51 -0
- snowflake/cli/api/entities/snowpark_entity.py +29 -0
- snowflake/cli/api/entities/streamlit_entity.py +12 -0
- snowflake/cli/api/entities/utils.py +357 -0
- snowflake/cli/api/exceptions.py +31 -5
- snowflake/cli/api/feature_flags.py +0 -1
- snowflake/cli/api/identifiers.py +41 -9
- snowflake/cli/api/project/definition.py +37 -6
- snowflake/cli/api/project/definition_conversion.py +194 -0
- snowflake/cli/api/project/definition_manager.py +12 -1
- snowflake/cli/api/project/project_verification.py +3 -3
- snowflake/cli/api/project/schemas/entities/{application_entity.py → application_entity_model.py} +21 -9
- snowflake/cli/api/project/schemas/entities/{application_package_entity.py → application_package_entity_model.py} +43 -15
- snowflake/cli/api/project/schemas/entities/common.py +80 -6
- snowflake/cli/api/project/schemas/entities/entities.py +38 -8
- snowflake/cli/api/project/schemas/entities/snowpark_entity.py +176 -0
- snowflake/cli/api/project/schemas/entities/streamlit_entity_model.py +73 -0
- snowflake/cli/api/project/schemas/identifier_model.py +10 -1
- snowflake/cli/api/project/schemas/native_app/application.py +8 -9
- snowflake/cli/api/project/schemas/native_app/package.py +7 -1
- snowflake/cli/api/project/schemas/project_definition.py +98 -27
- snowflake/cli/api/project/schemas/updatable_model.py +11 -3
- snowflake/cli/api/project/util.py +23 -6
- snowflake/cli/api/rendering/jinja.py +14 -8
- snowflake/cli/api/rendering/project_definition_templates.py +1 -1
- snowflake/cli/api/rendering/sql_templates.py +43 -11
- snowflake/cli/api/secure_path.py +16 -18
- snowflake/cli/api/secure_utils.py +90 -1
- snowflake/cli/api/sql_execution.py +48 -19
- snowflake/cli/api/utils/definition_rendering.py +18 -8
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +13 -13
- snowflake_cli_labs-3.0.0rc1.dist-info/RECORD +236 -0
- snowflake_cli_labs-3.0.0rc1.dist-info/entry_points.txt +2 -0
- snowflake/cli/api/commands/project_initialisation.py +0 -65
- snowflake/cli/app/build_and_push.sh +0 -8
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -172
- snowflake/cli/plugins/nativeapp/manager.py +0 -823
- snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
- snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
- snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
- snowflake/cli/plugins/snowpark/commands.py +0 -548
- snowflake/cli/plugins/snowpark/common.py +0 -307
- snowflake/cli/plugins/snowpark/manager.py +0 -109
- snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
- snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
- snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
- snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
- snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
- snowflake/cli/plugins/workspace/commands.py +0 -35
- snowflake/cli/templates/default_snowpark/.gitignore +0 -4
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
- snowflake/cli/templates/default_snowpark/app/common.py +0 -2
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
- snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
- snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
- snowflake/cli/templates/default_streamlit/.gitignore +0 -4
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
- snowflake/cli/templates/default_streamlit/environment.yml +0 -6
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
- snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
- snowflake_cli_labs-2.8.0rc1.dist-info/RECORD +0 -240
- snowflake_cli_labs-2.8.0rc1.dist-info/entry_points.txt +0 -2
- /snowflake/cli/{app → _app}/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/plugin/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/plugin/plugin_config_provider_impl.py +0 -0
- /snowflake/cli/{app → _app}/commands_registration/__init__.py +0 -0
- /snowflake/cli/{app → _app}/commands_registration/threadsafe.py +0 -0
- /snowflake/cli/{app → _app}/constants.py +0 -0
- /snowflake/cli/{app → _app}/dev/__init__.py +0 -0
- /snowflake/cli/{app → _app}/dev/commands_structure.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/__init__.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/project_definition_generate_json_schema.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/template_utils.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/templates/definition_description.rst.jinja2 +0 -0
- /snowflake/cli/{app → _app}/dev/docs/templates/overview.rst.jinja2 +0 -0
- /snowflake/cli/{app → _app}/dev/pycharm_remote_debug.py +0 -0
- /snowflake/cli/{app → _app}/loggers.py +0 -0
- /snowflake/cli/{plugins → _plugins}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/connection/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/cortex/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/cortex/types.py +0 -0
- /snowflake/cli/{plugins → _plugins}/git/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/init/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/sandbox.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/models.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/constants.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/feature_flags.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/policy.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/utils.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/version/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/notebook/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/notebook/exceptions.py +0 -0
- /snowflake/cli/{plugins → _plugins}/object/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/object/common.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/package/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/package/utils.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/common.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/compute_pool/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_registry/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_registry/manager.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_repository/__init__.py +0 -0
- /snowflake/cli/{plugins/spcs/jobs → _plugins/spcs/services}/__init__.py +0 -0
- /snowflake/cli/{plugins/spcs/services → _plugins/sql}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/sql/snowsql_templating.py +0 -0
- /snowflake/cli/{plugins/sql → _plugins/stage}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/stage/md5.py +0 -0
- /snowflake/cli/{plugins/stage → _plugins/streamlit}/__init__.py +0 -0
- /snowflake/cli/{plugins/streamlit → _plugins/workspace}/__init__.py +0 -0
- /snowflake/cli/{plugins/workspace → api/project/schemas/entities}/__init__.py +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,8 +17,10 @@ from __future__ import annotations
|
|
|
17
17
|
from typing import Dict, Optional
|
|
18
18
|
|
|
19
19
|
from click import ClickException
|
|
20
|
-
from jinja2 import StrictUndefined, loaders
|
|
21
|
-
from snowflake.cli.api.cli_global_context import
|
|
20
|
+
from jinja2 import Environment, StrictUndefined, loaders, meta
|
|
21
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
|
+
from snowflake.cli.api.console.console import cli_console
|
|
23
|
+
from snowflake.cli.api.exceptions import InvalidTemplate
|
|
22
24
|
from snowflake.cli.api.rendering.jinja import (
|
|
23
25
|
CONTEXT_KEY,
|
|
24
26
|
FUNCTION_KEY,
|
|
@@ -26,26 +28,55 @@ from snowflake.cli.api.rendering.jinja import (
|
|
|
26
28
|
env_bootstrap,
|
|
27
29
|
)
|
|
28
30
|
|
|
29
|
-
_SQL_TEMPLATE_START = "
|
|
30
|
-
_SQL_TEMPLATE_END = "
|
|
31
|
+
_SQL_TEMPLATE_START = "<%"
|
|
32
|
+
_SQL_TEMPLATE_END = "%>"
|
|
33
|
+
_OLD_SQL_TEMPLATE_START = "&{"
|
|
34
|
+
_OLD_SQL_TEMPLATE_END = "}"
|
|
31
35
|
RESERVED_KEYS = [CONTEXT_KEY, FUNCTION_KEY]
|
|
32
36
|
|
|
33
37
|
|
|
34
|
-
def
|
|
38
|
+
def _get_sql_jinja_env(template_start: str, template_end: str) -> Environment:
|
|
35
39
|
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
36
40
|
return env_bootstrap(
|
|
37
41
|
IgnoreAttrEnvironment(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
variable_end_string=_SQL_TEMPLATE_END,
|
|
42
|
+
variable_start_string=template_start,
|
|
43
|
+
variable_end_string=template_end,
|
|
44
|
+
loader=loaders.BaseLoader(),
|
|
42
45
|
block_start_string=_random_block,
|
|
43
46
|
block_end_string=_random_block,
|
|
47
|
+
keep_trailing_newline=True,
|
|
44
48
|
undefined=StrictUndefined,
|
|
45
49
|
)
|
|
46
50
|
)
|
|
47
51
|
|
|
48
52
|
|
|
53
|
+
def _does_template_have_env_syntax(env: Environment, template_content: str) -> bool:
|
|
54
|
+
template = env.parse(template_content)
|
|
55
|
+
return bool(meta.find_undeclared_variables(template))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def choose_sql_jinja_env_based_on_template_syntax(
|
|
59
|
+
template_content: str, reference_name: Optional[str] = None
|
|
60
|
+
) -> Environment:
|
|
61
|
+
old_syntax_env = _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
|
|
62
|
+
new_syntax_env = _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
|
|
63
|
+
has_old_syntax = _does_template_have_env_syntax(old_syntax_env, template_content)
|
|
64
|
+
has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
|
|
65
|
+
reference_name_str = f" in {reference_name}" if reference_name else ""
|
|
66
|
+
if has_old_syntax and has_new_syntax:
|
|
67
|
+
raise InvalidTemplate(
|
|
68
|
+
f"The SQL query{reference_name_str} mixes {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax"
|
|
69
|
+
f" and {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax."
|
|
70
|
+
)
|
|
71
|
+
if has_old_syntax:
|
|
72
|
+
cli_console.warning(
|
|
73
|
+
f"Warning: {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax{reference_name_str} is deprecated."
|
|
74
|
+
f" Use {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax instead."
|
|
75
|
+
)
|
|
76
|
+
return old_syntax_env
|
|
77
|
+
return new_syntax_env
|
|
78
|
+
|
|
79
|
+
|
|
49
80
|
def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
50
81
|
data = data or {}
|
|
51
82
|
|
|
@@ -55,6 +86,7 @@ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
|
55
86
|
f"{reserved_key} in user defined data. The `{reserved_key}` variable is reserved for CLI usage."
|
|
56
87
|
)
|
|
57
88
|
|
|
58
|
-
context_data =
|
|
89
|
+
context_data = get_cli_context().template_context
|
|
59
90
|
context_data.update(data)
|
|
60
|
-
|
|
91
|
+
env = choose_sql_jinja_env_based_on_template_syntax(content)
|
|
92
|
+
return env.from_string(content).render(context_data)
|
snowflake/cli/api/secure_path.py
CHANGED
|
@@ -24,6 +24,12 @@ from pathlib import Path
|
|
|
24
24
|
from typing import Optional, Union
|
|
25
25
|
|
|
26
26
|
from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError
|
|
27
|
+
from snowflake.cli.api.secure_utils import (
|
|
28
|
+
chmod as secure_chmod,
|
|
29
|
+
)
|
|
30
|
+
from snowflake.cli.api.secure_utils import (
|
|
31
|
+
restrict_file_permissions,
|
|
32
|
+
)
|
|
27
33
|
|
|
28
34
|
log = logging.getLogger(__name__)
|
|
29
35
|
|
|
@@ -47,6 +53,12 @@ class SecurePath:
|
|
|
47
53
|
"""
|
|
48
54
|
return self._path
|
|
49
55
|
|
|
56
|
+
def chmod(self, permissions_mask: int) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Change the file mode and permissions, like os.chmod().
|
|
59
|
+
"""
|
|
60
|
+
secure_chmod(self._path, permissions_mask)
|
|
61
|
+
|
|
50
62
|
@property
|
|
51
63
|
def parent(self):
|
|
52
64
|
"""
|
|
@@ -97,28 +109,11 @@ class SecurePath:
|
|
|
97
109
|
"""A string representing the final path component."""
|
|
98
110
|
return self._path.name
|
|
99
111
|
|
|
100
|
-
def chmod(self, permissions_mask: int) -> None:
|
|
101
|
-
"""
|
|
102
|
-
Change the file mode and permissions, like os.chmod().
|
|
103
|
-
"""
|
|
104
|
-
log.info(
|
|
105
|
-
"Update permissions of file %s to %s", self._path, oct(permissions_mask)
|
|
106
|
-
)
|
|
107
|
-
self._path.chmod(permissions_mask)
|
|
108
|
-
|
|
109
112
|
def restrict_permissions(self) -> None:
|
|
110
113
|
"""
|
|
111
114
|
Restrict file/directory permissions to owner-only.
|
|
112
115
|
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
owner_permissions = (
|
|
116
|
-
# https://docs.python.org/3/library/stat.html
|
|
117
|
-
stat.S_IRUSR # readable by owner
|
|
118
|
-
| stat.S_IWUSR # writeable by owner
|
|
119
|
-
| stat.S_IXUSR # executable by owner
|
|
120
|
-
)
|
|
121
|
-
self.chmod(self._path.stat().st_mode & owner_permissions)
|
|
116
|
+
restrict_file_permissions(self._path)
|
|
122
117
|
|
|
123
118
|
def touch(self, permissions_mask: int = 0o600, exist_ok: bool = True) -> None:
|
|
124
119
|
"""
|
|
@@ -349,6 +344,9 @@ class SecurePath:
|
|
|
349
344
|
):
|
|
350
345
|
raise FileTooLargeError(self._path.resolve(), size_limit_in_mb)
|
|
351
346
|
|
|
347
|
+
def rename(self, new_name: Union[str | Path]):
|
|
348
|
+
self._path.rename(new_name)
|
|
349
|
+
|
|
352
350
|
|
|
353
351
|
def _raise_file_exists_error(path: Path):
|
|
354
352
|
raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), path)
|
|
@@ -12,11 +12,64 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import logging
|
|
15
16
|
import stat
|
|
16
17
|
from pathlib import Path
|
|
18
|
+
from typing import List
|
|
17
19
|
|
|
20
|
+
from snowflake.connector.compat import IS_WINDOWS
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_windows_whitelisted_users():
|
|
26
|
+
# whitelisted users list obtained in consultation with prodsec: CASEC-9627
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
"SYSTEM",
|
|
31
|
+
"Administrators",
|
|
32
|
+
"Network",
|
|
33
|
+
"Domain Admins",
|
|
34
|
+
"Domain Users",
|
|
35
|
+
os.getlogin(),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _run_icacls(file_path: Path) -> str:
|
|
40
|
+
import subprocess
|
|
41
|
+
|
|
42
|
+
return subprocess.check_output(["icacls", str(file_path)], text=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _windows_permissions_are_denied(permission_codes: str) -> bool:
|
|
46
|
+
# according to https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
|
|
47
|
+
return "(DENY)" in permission_codes or "(N)" in permission_codes
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def windows_get_not_whitelisted_users_with_access(file_path: Path) -> List[str]:
|
|
51
|
+
import re
|
|
52
|
+
|
|
53
|
+
# according to https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
|
|
54
|
+
icacls_output_regex = (
|
|
55
|
+
rf"({re.escape(str(file_path))})?.*\\(?P<user>.*):(?P<permissions>[(A-Z),]+)"
|
|
56
|
+
)
|
|
57
|
+
whitelisted_users = _get_windows_whitelisted_users()
|
|
58
|
+
|
|
59
|
+
users_with_access = []
|
|
60
|
+
for permission in re.finditer(icacls_output_regex, _run_icacls(file_path)):
|
|
61
|
+
if (permission.group("user") not in whitelisted_users) and (
|
|
62
|
+
not _windows_permissions_are_denied(permission.group("permissions"))
|
|
63
|
+
):
|
|
64
|
+
users_with_access.append(permission.group("user"))
|
|
65
|
+
return list(set(users_with_access))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _windows_file_permissions_are_strict(file_path: Path) -> bool:
|
|
69
|
+
return windows_get_not_whitelisted_users_with_access(file_path) == []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _unix_file_permissions_are_strict(file_path: Path) -> bool:
|
|
20
73
|
accessible_by_others = (
|
|
21
74
|
# https://docs.python.org/3/library/stat.html
|
|
22
75
|
stat.S_IRGRP # readable by group
|
|
@@ -27,3 +80,39 @@ def file_permissions_are_strict(file_path: Path) -> bool:
|
|
|
27
80
|
| stat.S_IXOTH # executable by others
|
|
28
81
|
)
|
|
29
82
|
return (file_path.stat().st_mode & accessible_by_others) == 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def file_permissions_are_strict(file_path: Path) -> bool:
|
|
86
|
+
if IS_WINDOWS:
|
|
87
|
+
return _windows_file_permissions_are_strict(file_path)
|
|
88
|
+
return _unix_file_permissions_are_strict(file_path)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def chmod(path: Path, permissions_mask: int) -> None:
|
|
92
|
+
log.info("Update permissions of file %s to %s", path, oct(permissions_mask))
|
|
93
|
+
path.chmod(permissions_mask)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _unix_restrict_file_permissions(path: Path) -> None:
|
|
97
|
+
owner_permissions = (
|
|
98
|
+
# https://docs.python.org/3/library/stat.html
|
|
99
|
+
stat.S_IRUSR # readable by owner
|
|
100
|
+
| stat.S_IWUSR # writeable by owner
|
|
101
|
+
| stat.S_IXUSR # executable by owner
|
|
102
|
+
)
|
|
103
|
+
chmod(path, path.stat().st_mode & owner_permissions)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _windows_restrict_file_permissions(path: Path) -> None:
|
|
107
|
+
import subprocess
|
|
108
|
+
|
|
109
|
+
for user in windows_get_not_whitelisted_users_with_access(path):
|
|
110
|
+
log.info("Removing permissions of user %s from file %s", user, path)
|
|
111
|
+
subprocess.run(["icacls", str(path), "/DENY", f"{user}:F"])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def restrict_file_permissions(file_path: Path) -> None:
|
|
115
|
+
if IS_WINDOWS:
|
|
116
|
+
_windows_restrict_file_permissions(file_path)
|
|
117
|
+
else:
|
|
118
|
+
_unix_restrict_file_permissions(file_path)
|
|
@@ -21,7 +21,7 @@ from io import StringIO
|
|
|
21
21
|
from textwrap import dedent
|
|
22
22
|
from typing import Iterable, Optional, Tuple
|
|
23
23
|
|
|
24
|
-
from snowflake.cli.api.cli_global_context import
|
|
24
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
25
25
|
from snowflake.cli.api.console import cli_console
|
|
26
26
|
from snowflake.cli.api.constants import ObjectType
|
|
27
27
|
from snowflake.cli.api.exceptions import (
|
|
@@ -35,27 +35,21 @@ from snowflake.cli.api.project.util import (
|
|
|
35
35
|
unquote_identifier,
|
|
36
36
|
)
|
|
37
37
|
from snowflake.cli.api.utils.cursor import find_first_row
|
|
38
|
+
from snowflake.connector import SnowflakeConnection
|
|
38
39
|
from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
39
40
|
from snowflake.connector.errors import ProgrammingError
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
class
|
|
43
|
-
def __init__(self):
|
|
43
|
+
class SqlExecutor:
|
|
44
|
+
def __init__(self, connection: SnowflakeConnection | None = None):
|
|
44
45
|
self._snowpark_session = None
|
|
46
|
+
self._connection = connection
|
|
45
47
|
|
|
46
48
|
@property
|
|
47
|
-
def _conn(self):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def snowpark_session(self):
|
|
52
|
-
if not self._snowpark_session:
|
|
53
|
-
from snowflake.snowpark.session import Session
|
|
54
|
-
|
|
55
|
-
self._snowpark_session = Session.builder.configs(
|
|
56
|
-
{"connection": self._conn}
|
|
57
|
-
).create()
|
|
58
|
-
return self._snowpark_session
|
|
49
|
+
def _conn(self) -> SnowflakeConnection:
|
|
50
|
+
if self._connection:
|
|
51
|
+
return self._connection
|
|
52
|
+
return get_cli_context().connection
|
|
59
53
|
|
|
60
54
|
@cached_property
|
|
61
55
|
def _log(self):
|
|
@@ -88,6 +82,12 @@ class SqlExecutionMixin:
|
|
|
88
82
|
def _execute_queries(self, queries: str, **kwargs):
|
|
89
83
|
return list(self._execute_string(dedent(queries), **kwargs))
|
|
90
84
|
|
|
85
|
+
def execute_query(self, query: str, **kwargs):
|
|
86
|
+
return self._execute_query(query, **kwargs)
|
|
87
|
+
|
|
88
|
+
def execute_queries(self, queries: str, **kwargs):
|
|
89
|
+
return self._execute_queries(queries, **kwargs)
|
|
90
|
+
|
|
91
91
|
def use(self, object_type: ObjectType, name: str):
|
|
92
92
|
try:
|
|
93
93
|
self._execute_query(f"use {object_type.value.sf_name} {name}")
|
|
@@ -97,6 +97,13 @@ class SqlExecutionMixin:
|
|
|
97
97
|
f"Could not use {object_type} {name}. Object does not exist, or operation cannot be performed."
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
+
def current_role(self) -> str:
|
|
101
|
+
*_, cursor = self._execute_string(
|
|
102
|
+
"select current_role()", cursor_class=DictCursor
|
|
103
|
+
)
|
|
104
|
+
role_result = cursor.fetchone()
|
|
105
|
+
return role_result["CURRENT_ROLE()"]
|
|
106
|
+
|
|
100
107
|
@contextmanager
|
|
101
108
|
def use_role(self, new_role: str):
|
|
102
109
|
"""
|
|
@@ -117,6 +124,12 @@ class SqlExecutionMixin:
|
|
|
117
124
|
if is_different_role:
|
|
118
125
|
self._execute_query(f"use role {prev_role}")
|
|
119
126
|
|
|
127
|
+
def session_has_warehouse(self) -> bool:
|
|
128
|
+
result = self._execute_query(
|
|
129
|
+
"select current_warehouse() is not null as result", cursor_class=DictCursor
|
|
130
|
+
).fetchone()
|
|
131
|
+
return bool(result.get("RESULT"))
|
|
132
|
+
|
|
120
133
|
@contextmanager
|
|
121
134
|
def use_warehouse(self, new_wh: str):
|
|
122
135
|
"""
|
|
@@ -147,11 +160,11 @@ class SqlExecutionMixin:
|
|
|
147
160
|
self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)
|
|
148
161
|
|
|
149
162
|
def create_password_secret(
|
|
150
|
-
self, name:
|
|
163
|
+
self, name: FQN, username: str, password: str
|
|
151
164
|
) -> SnowflakeCursor:
|
|
152
165
|
return self._execute_query(
|
|
153
166
|
f"""
|
|
154
|
-
create secret {name}
|
|
167
|
+
create secret {name.sql_identifier}
|
|
155
168
|
type = password
|
|
156
169
|
username = '{username}'
|
|
157
170
|
password = '{password}'
|
|
@@ -159,11 +172,11 @@ class SqlExecutionMixin:
|
|
|
159
172
|
)
|
|
160
173
|
|
|
161
174
|
def create_api_integration(
|
|
162
|
-
self, name:
|
|
175
|
+
self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
|
|
163
176
|
) -> SnowflakeCursor:
|
|
164
177
|
return self._execute_query(
|
|
165
178
|
f"""
|
|
166
|
-
create api integration {name}
|
|
179
|
+
create api integration {name.sql_identifier}
|
|
167
180
|
api_provider = {api_provider}
|
|
168
181
|
api_allowed_prefixes = ('{allowed_prefix}')
|
|
169
182
|
allowed_authentication_secrets = ({secret if secret else ''})
|
|
@@ -254,6 +267,22 @@ class SqlExecutionMixin:
|
|
|
254
267
|
return show_obj_row
|
|
255
268
|
|
|
256
269
|
|
|
270
|
+
class SqlExecutionMixin(SqlExecutor):
|
|
271
|
+
def __init__(self, *args, **kwargs):
|
|
272
|
+
super().__init__(*args, **kwargs)
|
|
273
|
+
self._snowpark_session = None
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def snowpark_session(self):
|
|
277
|
+
if not self._snowpark_session:
|
|
278
|
+
from snowflake.snowpark.session import Session
|
|
279
|
+
|
|
280
|
+
self._snowpark_session = Session.builder.configs(
|
|
281
|
+
{"connection": self._conn}
|
|
282
|
+
).create()
|
|
283
|
+
return self._snowpark_session
|
|
284
|
+
|
|
285
|
+
|
|
257
286
|
class VerboseCursor(SnowflakeCursor):
|
|
258
287
|
def execute(self, command: str, *args, **kwargs):
|
|
259
288
|
cli_console.message(command)
|
|
@@ -26,9 +26,9 @@ from snowflake.cli.api.project.schemas.project_definition import (
|
|
|
26
26
|
build_project_definition,
|
|
27
27
|
)
|
|
28
28
|
from snowflake.cli.api.project.schemas.updatable_model import context
|
|
29
|
-
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
|
|
29
|
+
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY, FUNCTION_KEY
|
|
30
30
|
from snowflake.cli.api.rendering.project_definition_templates import (
|
|
31
|
-
|
|
31
|
+
get_client_side_jinja_env,
|
|
32
32
|
)
|
|
33
33
|
from snowflake.cli.api.utils.dict_utils import deep_merge_dicts, traverse
|
|
34
34
|
from snowflake.cli.api.utils.graph import Graph, Node
|
|
@@ -96,7 +96,7 @@ class TemplatedEnvironment:
|
|
|
96
96
|
)
|
|
97
97
|
or current_attr_chain is not None
|
|
98
98
|
):
|
|
99
|
-
raise InvalidTemplate(f"Unexpected
|
|
99
|
+
raise InvalidTemplate(f"Unexpected template syntax in {template_value}")
|
|
100
100
|
|
|
101
101
|
for child_node in ast_node.iter_child_nodes():
|
|
102
102
|
all_referenced_vars.update(
|
|
@@ -318,7 +318,7 @@ def render_definition_template(
|
|
|
318
318
|
if definition is None:
|
|
319
319
|
return ProjectProperties(None, {CONTEXT_KEY: {"env": environment_overrides}})
|
|
320
320
|
|
|
321
|
-
template_env = TemplatedEnvironment(
|
|
321
|
+
template_env = TemplatedEnvironment(get_client_side_jinja_env())
|
|
322
322
|
|
|
323
323
|
if "definition_version" not in definition or Version(
|
|
324
324
|
definition["definition_version"]
|
|
@@ -344,7 +344,7 @@ def render_definition_template(
|
|
|
344
344
|
_validate_env_section(definition.get("env", {}))
|
|
345
345
|
|
|
346
346
|
# add available templating functions
|
|
347
|
-
project_context[
|
|
347
|
+
project_context[FUNCTION_KEY] = get_templating_functions()
|
|
348
348
|
|
|
349
349
|
referenced_vars = _get_referenced_vars_in_definition(template_env, definition)
|
|
350
350
|
|
|
@@ -353,9 +353,7 @@ def render_definition_template(
|
|
|
353
353
|
)
|
|
354
354
|
|
|
355
355
|
def on_cycle_action(node: Node[TemplateVar]):
|
|
356
|
-
raise CycleDetectedError(
|
|
357
|
-
f"Cycle detected in templating variable {node.data.key}"
|
|
358
|
-
)
|
|
356
|
+
raise CycleDetectedError(f"Cycle detected in template variable {node.data.key}")
|
|
359
357
|
|
|
360
358
|
dependencies_graph.dfs(
|
|
361
359
|
visit_action=lambda node: _render_graph_node(template_env, node),
|
|
@@ -374,10 +372,22 @@ def render_definition_template(
|
|
|
374
372
|
)
|
|
375
373
|
|
|
376
374
|
project_definition = build_project_definition(**definition)
|
|
375
|
+
|
|
376
|
+
# Use the values originally provided by the user as the template context
|
|
377
|
+
# This intentionally doesn't reflect any field changes made by
|
|
378
|
+
# validators, to minimize user surprise when templating values
|
|
377
379
|
project_context[CONTEXT_KEY] = definition
|
|
380
|
+
|
|
378
381
|
# Use `ProjectEnvironment` in project context in order to
|
|
379
382
|
# handle env variables overrides from OS env and from CLI arguments.
|
|
380
383
|
project_context[CONTEXT_KEY]["env"] = ProjectEnvironment(
|
|
381
384
|
default_env=project_context[CONTEXT_KEY].get("env"), override_env=override_env
|
|
382
385
|
)
|
|
383
386
|
return ProjectProperties(project_definition, project_context)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def raw_project_properties(definition: Definition) -> ProjectProperties:
|
|
390
|
+
"""
|
|
391
|
+
Returns the raw project definition data without any templating.
|
|
392
|
+
"""
|
|
393
|
+
return ProjectProperties(build_project_definition(**definition), {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snowflake-cli-labs
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0rc1
|
|
4
4
|
Summary: Snowflake CLI
|
|
5
5
|
Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
|
|
@@ -216,7 +216,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
216
216
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
217
217
|
Classifier: Programming Language :: SQL
|
|
218
218
|
Classifier: Topic :: Database
|
|
219
|
-
Requires-Python: >=3.
|
|
219
|
+
Requires-Python: >=3.10
|
|
220
220
|
Requires-Dist: gitpython==3.1.43
|
|
221
221
|
Requires-Dist: jinja2==3.1.4
|
|
222
222
|
Requires-Dist: packaging
|
|
@@ -225,20 +225,20 @@ Requires-Dist: pluggy==1.5.0
|
|
|
225
225
|
Requires-Dist: pydantic==2.8.2
|
|
226
226
|
Requires-Dist: pyyaml==6.0.1
|
|
227
227
|
Requires-Dist: requests==2.32.3
|
|
228
|
-
Requires-Dist: requirements-parser==0.
|
|
228
|
+
Requires-Dist: requirements-parser==0.11.0
|
|
229
229
|
Requires-Dist: rich==13.7.1
|
|
230
|
-
Requires-Dist: setuptools==
|
|
231
|
-
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.
|
|
230
|
+
Requires-Dist: setuptools==74.1.0
|
|
231
|
+
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.12.1
|
|
232
232
|
Requires-Dist: snowflake-core==0.8.0; python_version < '3.12'
|
|
233
233
|
Requires-Dist: snowflake-snowpark-python>=1.15.0; python_version < '3.12'
|
|
234
|
-
Requires-Dist: tomlkit==0.13.
|
|
235
|
-
Requires-Dist: typer==0.12.
|
|
234
|
+
Requires-Dist: tomlkit==0.13.2
|
|
235
|
+
Requires-Dist: typer==0.12.5
|
|
236
236
|
Requires-Dist: urllib3<2.3,>=1.24.3
|
|
237
237
|
Provides-Extra: development
|
|
238
|
-
Requires-Dist: coverage==7.6.
|
|
238
|
+
Requires-Dist: coverage==7.6.1; extra == 'development'
|
|
239
239
|
Requires-Dist: pre-commit>=3.5.0; extra == 'development'
|
|
240
240
|
Requires-Dist: pytest-randomly==3.15.0; extra == 'development'
|
|
241
|
-
Requires-Dist: pytest==8.3.
|
|
241
|
+
Requires-Dist: pytest==8.3.2; extra == 'development'
|
|
242
242
|
Requires-Dist: syrupy==4.6.1; extra == 'development'
|
|
243
243
|
Description-Content-Type: text/markdown
|
|
244
244
|
|
|
@@ -282,12 +282,12 @@ Cheatsheet: https://github.com/Snowflake-Labs/sf-cheatsheets/blob/main/snowflake
|
|
|
282
282
|
|
|
283
283
|
## Install Snowflake CLI
|
|
284
284
|
|
|
285
|
-
### Install with
|
|
285
|
+
### Install with pipx (PyPi)
|
|
286
286
|
|
|
287
|
-
Requires Python >= 3.
|
|
287
|
+
We recommend installing Snowflake CLI in isolated environment using [pipx](https://pipx.pypa.io/stable/). Requires Python >= 3.10
|
|
288
288
|
|
|
289
289
|
```bash
|
|
290
|
-
|
|
290
|
+
pipx install snowflake-cli-labs
|
|
291
291
|
snow --help
|
|
292
292
|
```
|
|
293
293
|
|
|
@@ -303,7 +303,7 @@ snow --help
|
|
|
303
303
|
|
|
304
304
|
### Install from source
|
|
305
305
|
|
|
306
|
-
Requires Python >= 3.
|
|
306
|
+
Requires Python >= 3.10 and git
|
|
307
307
|
|
|
308
308
|
```bash
|
|
309
309
|
git clone https://github.com/snowflakedb/snowflake-cli
|