Skip to content

Commit fb5c8b5

Browse files
committed
SNOW-2306184: config refactor - more merging tests
1 parent 18a3932 commit fb5c8b5

File tree

11 files changed

+1279
-1042
lines changed

11 files changed

+1279
-1042
lines changed

src/snowflake/cli/api/config_ng/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
CliEnvironment,
4949
CliParameters,
5050
ConnectionsConfigFile,
51+
ConnectionSpecificEnvironment,
5152
SnowSQLConfigFile,
5253
SnowSQLEnvironment,
5354
)
@@ -60,6 +61,7 @@
6061
"ConfigurationResolver",
6162
"ConfigValue",
6263
"ConnectionsConfigFile",
64+
"ConnectionSpecificEnvironment",
6365
"explain_configuration",
6466
"export_resolution_history",
6567
"format_summary_for_display",

src/snowflake/cli/api/config_ng/sources.py

Lines changed: 114 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -448,78 +448,69 @@ def supports_key(self, key: str) -> bool:
448448
return False
449449

450450

451-
class CliEnvironment(ValueSource):
451+
# Base configuration keys that can be set via environment
452+
_ENV_CONFIG_KEYS = [
453+
"account",
454+
"user",
455+
"password",
456+
"database",
457+
"schema",
458+
"role",
459+
"warehouse",
460+
"protocol",
461+
"host",
462+
"port",
463+
"region",
464+
"authenticator",
465+
"workload_identity_provider",
466+
"private_key_file",
467+
"private_key_path", # Used by integration tests
468+
"private_key_raw", # Used by integration tests
469+
"private_key_passphrase", # Private key passphrase for encrypted keys
470+
"token", # OAuth token
471+
"session_token", # Session token for session-based authentication
472+
"master_token", # Master token for advanced authentication
473+
"token_file_path",
474+
"oauth_client_id",
475+
"oauth_client_secret",
476+
"oauth_authorization_url",
477+
"oauth_token_request_url",
478+
"oauth_redirect_uri",
479+
"oauth_scope",
480+
"oauth_enable_pkce", # Fixed typo: was "oatuh_enable_pkce"
481+
"oauth_enable_refresh_tokens",
482+
"oauth_enable_single_use_refresh_tokens",
483+
"client_store_temporary_credential",
484+
]
485+
486+
487+
class ConnectionSpecificEnvironment(ValueSource):
452488
"""
453-
CLI environment variables source.
454-
455-
Discovers SNOWFLAKE_* environment variables with two patterns:
456-
1. General: SNOWFLAKE_ACCOUNT (applies to all connections)
457-
2. Connection-specific: SNOWFLAKE_CONNECTIONS_<name>_ACCOUNT (overrides general)
489+
Connection-specific environment variables source.
458490
459-
Connection-specific variables take precedence within this source.
491+
Discovers SNOWFLAKE_CONNECTIONS_<name>_<key> environment variables.
492+
Returns prefixed keys: connections.{name}.{key}
460493
461494
Examples:
462-
SNOWFLAKE_ACCOUNT -> account (general)
463-
SNOWFLAKE_CONNECTIONS_INTEGRATION_ACCOUNT -> account (for "integration" connection)
464-
SNOWFLAKE_USER -> user
465-
SNOWFLAKE_CONNECTIONS_DEV_USER -> user (for "dev" connection)
495+
SNOWFLAKE_CONNECTIONS_INTEGRATION_ACCOUNT=x -> connections.integration.account=x
496+
SNOWFLAKE_CONNECTIONS_DEV_USER=y -> connections.dev.user=y
466497
"""
467498

468-
# Base configuration keys that can be set via environment
469-
CONFIG_KEYS = [
470-
"account",
471-
"user",
472-
"password",
473-
"database",
474-
"schema",
475-
"role",
476-
"warehouse",
477-
"protocol",
478-
"host",
479-
"port",
480-
"region",
481-
"authenticator",
482-
"workload_identity_provider",
483-
"private_key_file",
484-
"private_key_path", # Used by integration tests
485-
"private_key_raw", # Used by integration tests
486-
"private_key_passphrase", # Private key passphrase for encrypted keys
487-
"token", # OAuth token
488-
"session_token", # Session token for session-based authentication
489-
"master_token", # Master token for advanced authentication
490-
"token_file_path",
491-
"oauth_client_id",
492-
"oauth_client_secret",
493-
"oauth_authorization_url",
494-
"oauth_token_request_url",
495-
"oauth_redirect_uri",
496-
"oauth_scope",
497-
"oauth_enable_pkce", # Fixed typo: was "oatuh_enable_pkce"
498-
"oauth_enable_refresh_tokens",
499-
"oauth_enable_single_use_refresh_tokens",
500-
"client_store_temporary_credential",
501-
]
502-
503499
@property
504500
def source_name(self) -> str:
505-
return "cli_env"
501+
return "connection_specific_env"
506502

507503
def discover(self, key: Optional[str] = None) -> Dict[str, ConfigValue]:
508504
"""
509-
Discover SNOWFLAKE_* environment variables.
510-
Returns both general (flat) and connection-specific (prefixed) keys.
505+
Discover SNOWFLAKE_CONNECTIONS_* environment variables.
506+
Returns connection-specific (prefixed) keys only.
511507
512-
Patterns:
513-
1. SNOWFLAKE_ACCOUNT=x -> account=x (flat key)
514-
2. SNOWFLAKE_CONNECTIONS_INTEGRATION_ACCOUNT=y -> connections.integration.account=y
508+
Pattern: SNOWFLAKE_CONNECTIONS_<NAME>_<KEY>=value -> connections.{name}.{key}=value
515509
"""
516510
values: Dict[str, ConfigValue] = {}
517511

518512
# Scan all environment variables
519513
for env_name, env_value in os.environ.items():
520-
if not env_name.startswith("SNOWFLAKE_"):
521-
continue
522-
523514
# Check for connection-specific pattern: SNOWFLAKE_CONNECTIONS_<NAME>_<KEY>
524515
if env_name.startswith("SNOWFLAKE_CONNECTIONS_"):
525516
# Extract connection name and config key
@@ -530,7 +521,7 @@ def discover(self, key: Optional[str] = None) -> Dict[str, ConfigValue]:
530521
conn_name = conn_name_upper.lower()
531522
config_key = config_key_upper.lower()
532523

533-
if config_key in self.CONFIG_KEYS:
524+
if config_key in _ENV_CONFIG_KEYS:
534525
full_key = f"connections.{conn_name}.{config_key}"
535526
if key is None or full_key == key:
536527
values[full_key] = ConfigValue(
@@ -540,40 +531,79 @@ def discover(self, key: Optional[str] = None) -> Dict[str, ConfigValue]:
540531
raw_value=f"{env_name}={env_value}",
541532
)
542533

534+
return values
535+
536+
def supports_key(self, key: str) -> bool:
537+
# Check if key matches pattern connections.{name}.{param}
538+
if key.startswith("connections."):
539+
parts = key.split(".", 2)
540+
if len(parts) == 3:
541+
_, conn_name, config_key = parts
542+
env_var = (
543+
f"SNOWFLAKE_CONNECTIONS_{conn_name.upper()}_{config_key.upper()}"
544+
)
545+
return os.getenv(env_var) is not None
546+
return False
547+
548+
549+
class CliEnvironment(ValueSource):
550+
"""
551+
CLI general environment variables source.
552+
553+
Discovers general SNOWFLAKE_* environment variables (not connection-specific).
554+
Returns flat keys that apply to all connections.
555+
556+
Examples:
557+
SNOWFLAKE_ACCOUNT -> account (general, applies to all connections)
558+
SNOWFLAKE_USER -> user
559+
SNOWFLAKE_PASSWORD -> password
560+
"""
561+
562+
@property
563+
def source_name(self) -> str:
564+
return "cli_env"
565+
566+
def discover(self, key: Optional[str] = None) -> Dict[str, ConfigValue]:
567+
"""
568+
Discover general SNOWFLAKE_* environment variables.
569+
Returns general (flat) keys only.
570+
571+
Pattern: SNOWFLAKE_<KEY>=value -> {key}=value
572+
"""
573+
values: Dict[str, ConfigValue] = {}
574+
575+
# Scan all environment variables
576+
for env_name, env_value in os.environ.items():
577+
if not env_name.startswith("SNOWFLAKE_"):
578+
continue
579+
580+
# Skip connection-specific variables
581+
if env_name.startswith("SNOWFLAKE_CONNECTIONS_"):
582+
continue
583+
543584
# Check for general pattern: SNOWFLAKE_<KEY>
544-
elif not env_name.startswith("SNOWFLAKE_CONNECTIONS_"):
545-
config_key_upper = env_name[len("SNOWFLAKE_") :]
546-
config_key = config_key_upper.lower()
547-
548-
if config_key in self.CONFIG_KEYS:
549-
if key is None or config_key == key:
550-
values[config_key] = ConfigValue(
551-
key=config_key,
552-
value=env_value,
553-
source_name=self.source_name,
554-
raw_value=f"{env_name}={env_value}",
555-
)
585+
config_key_upper = env_name[len("SNOWFLAKE_") :]
586+
config_key = config_key_upper.lower()
587+
588+
if config_key in _ENV_CONFIG_KEYS:
589+
if key is None or config_key == key:
590+
values[config_key] = ConfigValue(
591+
key=config_key,
592+
value=env_value,
593+
source_name=self.source_name,
594+
raw_value=f"{env_name}={env_value}",
595+
)
556596

557597
return values
558598

559599
def supports_key(self, key: str) -> bool:
560-
discovered = self.discover()
561-
if key in discovered:
562-
return True
563-
564-
# Check general var
565-
if os.getenv(f"SNOWFLAKE_{key.upper()}") is not None:
566-
return True
567-
568-
# Check connection-specific var
569-
if hasattr(self, "_connection_name") and self._connection_name:
570-
conn_var = (
571-
f"SNOWFLAKE_CONNECTIONS_{self._connection_name.upper()}_{key.upper()}"
572-
)
573-
if os.getenv(conn_var) is not None:
574-
return True
600+
# Only support flat keys (not prefixed with connections.)
601+
if "." in key:
602+
return False
575603

576-
return False
604+
# Check if the general env var exists
605+
env_var = f"SNOWFLAKE_{key.upper()}"
606+
return os.getenv(env_var) is not None
577607

578608

579609
class CliParameters(ValueSource):

0 commit comments

Comments
 (0)