Skip to content

Commit

Permalink
Merge branch 'main' into gbloom-SNOW-1215781-app-deploy-command
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-gbloom authored Mar 20, 2024
2 parents f3ee944 + ef1c404 commit a02559f
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ repos:
- id: dependencies-sync
name: "Copy dependencies from pyproject.toml to requirements.txt"
language: system
entry: python .snyk/dependency-sync.py
entry: python snyk/dependency-sync.py
files: ^pyproject.toml$
5 changes: 5 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
* Changing imports in function/procedure section in `snowflake.yml` will cause the definition update on replace
* Adding `--pattern` flag to `stage list` command for filtering out results with regex.

# v2.1.1

## Fixes and improvements
* Improved security of printing connection details in `snow connection list`.

# v2.1.0

## Backward incompatibility
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies = [
"typer==0.9.0",
"urllib3>=1.21.1,<2.3",
"GitPython==3.1.42",
"pydantic==2.6.3"
"pydantic==2.6.4"
]
classifiers = [
"Development Status :: 3 - Alpha",
Expand All @@ -39,7 +39,7 @@ classifiers = [

[project.optional-dependencies]
development = [
"coverage==7.4.3",
"coverage==7.4.4",
"pre-commit>=3.5.0",
"pytest==8.1.1",
"pytest-randomly==3.15.0",
Expand Down
3 changes: 1 addition & 2 deletions .snyk/dependency-sync.py → snyk/dependency-sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ def sync():
pyproject = tomlkit.loads(Path("pyproject.toml").read_text())
dependencies = pyproject["project"]["dependencies"]
dev_dependencies = pyproject["project"]["optional-dependencies"]["development"]
with open(".snyk/req-auto-generated.txt", "w") as req:
req.write("# Auto generated\n")
with open("snyk/requirements.txt", "w") as req:
for dep in dependencies:
req.write(f"{dep}\n")
for dep in dev_dependencies:
Expand Down
5 changes: 2 additions & 3 deletions .snyk/req-auto-generated.txt → snyk/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Auto generated
jinja2==3.1.3
pluggy==1.4.0
PyYAML==6.0.1
Expand All @@ -11,8 +10,8 @@ tomlkit==0.12.3
typer==0.9.0
urllib3>=1.21.1,<2.3
GitPython==3.1.42
pydantic==2.6.3
coverage==7.4.3
pydantic==2.6.4
coverage==7.4.4
pre-commit>=3.5.0
pytest==8.1.1
pytest-randomly==3.15.0
Expand Down
71 changes: 64 additions & 7 deletions src/snowflake/cli/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import os
from contextlib import contextmanager
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any, Dict, Optional, Union

Expand Down Expand Up @@ -47,6 +48,51 @@ class Empty:
)


@dataclass
class ConnectionConfig:
account: Optional[str] = None
user: Optional[str] = None
password: Optional[str] = field(default=None, repr=False)
host: Optional[str] = None
region: Optional[str] = None
port: Optional[int] = None
database: Optional[str] = None
schema: Optional[str] = None
warehouse: Optional[str] = None
role: Optional[str] = None
authenticator: Optional[str] = None
private_key_path: Optional[str] = None

_other_settings: dict = field(default_factory=lambda: {})

@classmethod
def from_dict(cls, config_dict: dict) -> ConnectionConfig:
known_settings = {}
other_settings = {}
for key, value in config_dict.items():
if key in cls.__dict__:
known_settings[key] = value
else:
other_settings[key] = value
return cls(**known_settings, _other_settings=other_settings)

def to_dict_of_known_non_empty_values(self) -> dict:
return {
k: v
for k, v in asdict(self).items()
if k != "_other_settings" and v is not None
}

def _non_empty_other_values(self) -> dict:
return {k: v for k, v in self._other_settings.items() if v is not None}

def to_dict_of_all_non_empty_values(self) -> dict:
return {
**self.to_dict_of_known_non_empty_values(),
**self._non_empty_other_values(),
}


def config_init(config_file: Optional[Path]):
"""
Initializes the app configuration. Config provided via cli flag takes precedence.
Expand All @@ -61,8 +107,12 @@ def config_init(config_file: Optional[Path]):
CONFIG_MANAGER.read_config()


def add_connection(name: str, parameters: dict):
set_config_value(section=CONNECTIONS_SECTION, key=name, value=parameters)
def add_connection(name: str, connection_config: ConnectionConfig):
set_config_value(
section=CONNECTIONS_SECTION,
key=name,
value=connection_config.to_dict_of_all_non_empty_values(),
)


_DEFAULT_LOGS_CONFIG = {
Expand Down Expand Up @@ -126,16 +176,23 @@ def config_section_exists(*path) -> bool:
return False


def get_connection(connection_name: str) -> dict:
def get_all_connections() -> dict[str, ConnectionConfig]:
return {
k: ConnectionConfig.from_dict(connection_dict)
for k, connection_dict in get_config_section("connections").items()
}


def get_connection_dict(connection_name: str) -> dict:
try:
return get_config_section(CONNECTIONS_SECTION, connection_name)
except KeyError:
raise MissingConfiguration(f"Connection {connection_name} is not configured")


def get_default_connection() -> dict:
def get_default_connection_dict() -> dict:
def_connection_name = CONFIG_MANAGER["default_connection_name"]
return get_connection(def_connection_name)
return get_connection_dict(def_connection_name)


def get_config_section(*path) -> dict:
Expand Down Expand Up @@ -215,9 +272,9 @@ def _merge_section_with_env(section: Union[Table, Any], *path) -> Dict[str, str]


def _get_envs_for_path(*path) -> dict:
env_variables_prefix = "SNOWFLAKE_" + "_".join(p.upper() for p in path)
env_variables_prefix = "_".join(["SNOWFLAKE"] + [p.upper() for p in path]) + "_"
return {
k.replace(f"{env_variables_prefix}_", "").lower(): os.environ[k]
k.replace(env_variables_prefix, "").lower(): os.environ[k]
for k in os.environ.keys()
if k.startswith(env_variables_prefix)
}
Expand Down
6 changes: 3 additions & 3 deletions src/snowflake/cli/app/snow_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import snowflake.connector
from click.exceptions import ClickException
from snowflake.cli.api.config import get_connection, get_default_connection
from snowflake.cli.api.config import get_connection_dict, get_default_connection_dict
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
from snowflake.cli.api.exceptions import (
InvalidConnectionConfiguration,
Expand Down Expand Up @@ -37,11 +37,11 @@ def connect_to_snowflake(
raise ClickException("Can't use connection name and temporary connection.")

if connection_name:
connection_parameters = get_connection(connection_name)
connection_parameters = get_connection_dict(connection_name)
elif temporary_connection:
connection_parameters = {} # we will apply overrides in next step
else:
connection_parameters = get_default_connection()
connection_parameters = get_default_connection_dict()

# Apply overrides to connection details
for key, value in overrides.items():
Expand Down
52 changes: 29 additions & 23 deletions src/snowflake/cli/plugins/connection/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
)
from snowflake.cli.api.commands.snow_typer import SnowTyper
from snowflake.cli.api.config import (
ConnectionConfig,
add_connection,
connection_exists,
get_config_section,
get_connection,
get_all_connections,
get_connection_dict,
set_config_value,
)
from snowflake.cli.api.console import cli_console
Expand Down Expand Up @@ -63,10 +64,15 @@ def list_connections(**options) -> CommandResult:
"""
Lists configured connections.
"""
connections = get_config_section("connections")
connections = get_all_connections()
result = (
{"connection_name": k, "parameters": _mask_password(v)}
for k, v in connections.items()
{
"connection_name": connection_name,
"parameters": _mask_password(
connection_config.to_dict_of_known_non_empty_values()
),
}
for connection_name, connection_config in connections.items()
)
return CollectionResult(result)

Expand Down Expand Up @@ -203,26 +209,26 @@ def add(
**options,
) -> CommandResult:
"""Adds a connection to configuration file."""
connection_entry = {
"account": account,
"user": user,
"password": password,
"host": host,
"region": region,
"port": port,
"database": database,
"schema": schema,
"warehouse": warehouse,
"role": role,
"authenticator": authenticator,
"private_key_path": private_key_path,
}
connection_entry = {k: v for k, v in connection_entry.items() if v is not None}

if connection_exists(connection_name):
raise ClickException(f"Connection {connection_name} already exists")

add_connection(connection_name, connection_entry)
add_connection(
connection_name,
ConnectionConfig(
account=account,
user=user,
password=password,
host=host,
region=region,
port=port,
database=database,
schema=schema,
warehouse=warehouse,
role=role,
authenticator=authenticator,
private_key_path=private_key_path,
),
)
return MessageResult(
f"Wrote new connection {connection_name} to {CONFIG_MANAGER.file_path}"
)
Expand Down Expand Up @@ -289,6 +295,6 @@ def set_default(
**options,
):
"""Changes default connection to provided value."""
get_connection(connection_name=name)
get_connection_dict(connection_name=name)
set_config_value(section=None, key="default_connection_name", value=name)
return MessageResult(f"Default connection set to: {name}")
2 changes: 1 addition & 1 deletion src/snowflake/cli/plugins/snowpark/venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def pip_download(self, requirements_files, download_dir):
return process.returncode

def _create_venv(self):
venv.create(self.directory.name, self.with_pip)
venv.create(self.directory.name, with_pip=self.with_pip)

@staticmethod
def _get_python_path(venv_dir: Path) -> Path:
Expand Down
18 changes: 9 additions & 9 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
ConfigFileTooWidePermissionsError,
config_init,
get_config_section,
get_connection,
get_default_connection,
get_connection_dict,
get_default_connection_dict,
)
from snowflake.cli.api.exceptions import MissingConfiguration

Expand All @@ -31,7 +31,7 @@ def test_empty_config_file_is_created_if_not_present():
def test_get_connection_from_file(test_snowcli_config):
config_init(test_snowcli_config)

assert get_connection("full") == {
assert get_connection_dict("full") == {
"account": "dev_account",
"user": "dev_user",
"host": "dev_host",
Expand All @@ -57,7 +57,7 @@ def test_get_connection_from_file(test_snowcli_config):
def test_environment_variables_override_configuration_value(test_snowcli_config):
config_init(test_snowcli_config)

assert get_connection("default") == {
assert get_connection_dict("default") == {
"database": "database_foo",
"schema": "test_public",
"role": "test_role",
Expand All @@ -79,7 +79,7 @@ def test_environment_variables_override_configuration_value(test_snowcli_config)
def test_environment_variables_works_if_config_value_not_present(test_snowcli_config):
config_init(test_snowcli_config)

assert get_connection("empty") == {
assert get_connection_dict("empty") == {
"account": "some_account",
"database": "test_database",
"warehouse": "large",
Expand Down Expand Up @@ -144,7 +144,7 @@ def test_create_default_config_if_not_exists(mock_config_manager):
def test_default_connection_with_overwritten_values(test_snowcli_config):
config_init(test_snowcli_config)

assert get_default_connection() == {
assert get_default_connection_dict() == {
"database": "db_for_test",
"role": "test_role",
"schema": "test_public",
Expand All @@ -157,7 +157,7 @@ def test_default_connection_with_overwritten_values(test_snowcli_config):
def test_not_found_default_connection(test_root_path):
config_init(Path(test_root_path / "empty_config.toml"))
with pytest.raises(MissingConfiguration) as ex:
get_default_connection()
get_default_connection_dict()

assert ex.value.message == "Connection default is not configured"

Expand All @@ -172,7 +172,7 @@ def test_not_found_default_connection(test_root_path):
def test_not_found_default_connection_from_evn_variable(test_root_path):
config_init(Path(test_root_path / "empty_config.toml"))
with pytest.raises(MissingConfiguration) as ex:
get_default_connection()
get_default_connection_dict()

assert ex.value.message == "Connection not_existed_connection is not configured"

Expand All @@ -188,7 +188,7 @@ def test_connections_toml_override_config_toml(test_snowcli_config, snowflake_ho
)
config_init(test_snowcli_config)

assert get_default_connection() == {"database": "overridden_database"}
assert get_default_connection_dict() == {"database": "overridden_database"}
assert CONFIG_MANAGER["connections"] == {
"default": {"database": "overridden_database"}
}
Expand Down
Loading

0 comments on commit a02559f

Please sign in to comment.