diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8502130953..7018dae16f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -32,13 +32,36 @@ * Add ability to add and remove accounts from release channels through `snow app release-channel add-accounts` and snow app release-channel remove-accounts` commands. * Add ability to add/remove versions to/from release channels through `snow app release-channel add-version` and `snow app release-channel remove-version` commands. * Add publish command to make it easier to manage publishing versions to release channels and updating release directives: `snow app publish` +* Add support for restricting Snowflake user authentication policy to Snowflake CLI-only. ## Fixes and improvements -* Fixed crashes with older x86_64 Intel CPUs. * Fixed inability to add patches to lowercase quoted versions * Fixes label being set to blank instead of None when not provided. * Added a feature flag `ENABLE_SPCS_LOG_STREAMING` to control the rollout of the log streaming feature + +# v3.2.2 +## Backward incompatibility + +## Deprecations + +## New additions + +## Fixes and improvements +* Fix "No module named 'pandas'" warning. + + +# v3.2.1 +## Backward incompatibility + +## Deprecations + +## New additions + +## Fixes and improvements +* Fixed crashes with older x86_64 Intel CPUs. + + # v3.2.0 ## Deprecations diff --git a/pyproject.toml b/pyproject.toml index 8df59c740e..f49ec88f86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ requires-python = ">=3.10" description = "Snowflake CLI" readme = "README.md" dependencies = [ + "click==8.1.7", "jinja2==3.1.4", "pluggy==1.5.0", "PyYAML==6.0.2", diff --git a/snyk/requirements.txt b/snyk/requirements.txt index e5870050d1..4821fede48 100644 --- a/snyk/requirements.txt +++ b/snyk/requirements.txt @@ -1,3 +1,4 @@ +click==8.1.7 jinja2==3.1.4 pluggy==1.5.0 PyYAML==6.0.2 diff --git a/src/snowflake/cli/_app/constants.py b/src/snowflake/cli/_app/constants.py index 6f716e0d8d..c5c715a466 100644 --- a/src/snowflake/cli/_app/constants.py +++ b/src/snowflake/cli/_app/constants.py @@ -17,3 +17,7 @@ from typing import Literal PARAM_APPLICATION_NAME: Literal["snowcli"] = "snowcli" + +# This is also defined on server side. Changing this parameter would require +# a change in https://github.com/snowflakedb/snowflake +INTERNAL_APPLICATION_NAME: Literal["SNOWFLAKE_CLI"] = "SNOWFLAKE_CLI" diff --git a/src/snowflake/cli/_app/snow_connector.py b/src/snowflake/cli/_app/snow_connector.py index ccd48c75c4..943bc70179 100644 --- a/src/snowflake/cli/_app/snow_connector.py +++ b/src/snowflake/cli/_app/snow_connector.py @@ -21,7 +21,9 @@ import snowflake.connector from click.exceptions import ClickException +from snowflake.cli.__about__ import VERSION from snowflake.cli._app.constants import ( + INTERNAL_APPLICATION_NAME, PARAM_APPLICATION_NAME, ) from snowflake.cli._app.secret import SecretType @@ -35,6 +37,7 @@ InvalidConnectionConfiguration, SnowflakeConnectionError, ) +from snowflake.cli.api.feature_flags import FeatureFlag from snowflake.cli.api.secure_path import SecurePath from snowflake.connector import SnowflakeConnection from snowflake.connector.errors import DatabaseError, ForbiddenError @@ -150,6 +153,8 @@ def connect_to_snowflake( _update_connection_application_name(connection_parameters) + _update_internal_application_info(connection_parameters) + try: # Whatever output is generated when creating connection, # we don't want it in our output. This is particularly important @@ -238,6 +243,13 @@ def _update_connection_application_name(connection_parameters: Dict): connection_parameters.update(connection_application_params) +def _update_internal_application_info(connection_parameters: Dict): + """Update internal application data if ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID is enabled.""" + if FeatureFlag.ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID.is_enabled(): + connection_parameters["internal_application_name"] = INTERNAL_APPLICATION_NAME + connection_parameters["internal_application_version"] = VERSION + + def _load_pem_from_file(private_key_file: str) -> SecretType: with SecurePath(private_key_file).open( "rb", read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB diff --git a/src/snowflake/cli/api/feature_flags.py b/src/snowflake/cli/api/feature_flags.py index 2a56458083..478768a318 100644 --- a/src/snowflake/cli/api/feature_flags.py +++ b/src/snowflake/cli/api/feature_flags.py @@ -64,3 +64,6 @@ class FeatureFlag(FeatureFlagMixin): "ENABLE_STREAMLIT_VERSIONED_STAGE", False ) ENABLE_SPCS_LOG_STREAMING = BooleanFlag("ENABLE_SPCS_LOG_STREAMING", False) + ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID = BooleanFlag( + "ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID", False + ) diff --git a/tests/test_snow_connector.py b/tests/test_snow_connector.py index 302bf684bd..93c69b031b 100644 --- a/tests/test_snow_connector.py +++ b/tests/test_snow_connector.py @@ -222,3 +222,32 @@ def test_returns_nice_error_in_case_of_missing_master_token(runner): "When using a session token, you must provide the corresponding master token" in result.output ) + + +@mock.patch("snowflake.connector.connect") +@pytest.mark.parametrize("feature_flag", [None, True, False]) +def test_internal_application_data_is_sent_if_feature_flag_is_set( + mock_connect, runner, feature_flag +): + expected_kwargs = { + "application": "SNOWCLI.SQL", + "database": "db_for_test", + "schema": "test_public", + "role": "test_role", + "warehouse": "xs", + "password": "dummy_password", + "application_name": "snowcli", + } + env = {} + if feature_flag is not None: + env["SNOWFLAKE_CLI_FEATURES_ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID"] = str( + feature_flag + ) + if feature_flag: + # internal app data should be disabled by default + expected_kwargs["internal_application_name"] = "SNOWFLAKE_CLI" + expected_kwargs["internal_application_version"] = "0.0.0-test_patched" + with mock.patch.dict(os.environ, env): + result = runner.invoke(["sql", "-q", "select 1"]) + assert result.exit_code == 0 + mock_connect.assert_called_once_with(**expected_kwargs)