Skip to content

Commit a0c7653

Browse files
committed
SNOW-2306184: config refactory - env vars discovery
1 parent 5deaf40 commit a0c7653

File tree

5 files changed

+1141
-0
lines changed

5 files changed

+1141
-0
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
SourcePriority,
3030
ValueSource,
3131
)
32+
from snowflake.cli.api.config_ng.env_handlers import (
33+
SnowCliEnvHandler,
34+
SnowSqlEnvHandler,
35+
)
3236
from snowflake.cli.api.config_ng.sources import (
3337
CliArgumentSource,
3438
ConfigurationSource,
@@ -44,6 +48,8 @@
4448
"FileSource",
4549
"ResolutionEntry",
4650
"ResolutionHistory",
51+
"SnowCliEnvHandler",
52+
"SnowSqlEnvHandler",
4753
"SourcePriority",
4854
"ValueSource",
4955
]
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
"""
16+
Environment variable handlers for configuration system.
17+
18+
This module implements handlers for:
19+
- SNOWFLAKE_* environment variables (SnowCLI format)
20+
- SNOWSQL_* environment variables (Legacy SnowSQL format with key mapping)
21+
"""
22+
23+
from __future__ import annotations
24+
25+
import os
26+
from typing import Any, Dict, Optional
27+
28+
from snowflake.cli.api.config_ng.core import ConfigValue, SourcePriority
29+
from snowflake.cli.api.config_ng.handlers import SourceHandler
30+
31+
32+
class SnowCliEnvHandler(SourceHandler):
33+
"""
34+
Handler for Snowflake CLI environment variables.
35+
Format: SNOWFLAKE_<KEY> → key
36+
Example: SNOWFLAKE_ACCOUNT → account
37+
"""
38+
39+
PREFIX = "SNOWFLAKE_"
40+
41+
@property
42+
def source_name(self) -> str:
43+
return "snowflake_cli_env"
44+
45+
@property
46+
def priority(self) -> SourcePriority:
47+
return SourcePriority.ENVIRONMENT
48+
49+
@property
50+
def handler_type(self) -> str:
51+
return "snowflake_cli_env"
52+
53+
def can_handle(self) -> bool:
54+
"""Check if any SNOWFLAKE_* env vars are set."""
55+
return any(k.startswith(self.PREFIX) for k in os.environ)
56+
57+
def discover(self, key: Optional[str] = None) -> Dict[str, ConfigValue]:
58+
"""Discover values from SNOWFLAKE_* environment variables."""
59+
values = {}
60+
61+
if key is not None:
62+
# Discover specific key
63+
env_key = f"{self.PREFIX}{key.upper()}"
64+
if env_key in os.environ:
65+
raw = os.environ[env_key]
66+
values[key] = ConfigValue(
67+
key=key,
68+
value=self._parse_value(raw),
69+
source_name=self.source_name,
70+
priority=self.priority,
71+
raw_value=raw,
72+
)
73+
else:
74+
# Discover all SNOWFLAKE_* variables
75+
for env_key, env_value in os.environ.items():
76+
if env_key.startswith(self.PREFIX):
77+
config_key = env_key[len(self.PREFIX) :].lower()
78+
values[config_key] = ConfigValue(
79+
key=config_key,
80+
value=self._parse_value(env_value),
81+
source_name=self.source_name,
82+
priority=self.priority,
83+
raw_value=env_value,
84+
)
85+
86+
return values
87+
88+
def supports_key(self, key: str) -> bool:
89+
"""Any string key can be represented as SNOWFLAKE_* env var."""
90+
return isinstance(key, str)
91+
92+
def _parse_value(self, value: str) -> Any:
93+
"""
94+
Parse string value to appropriate type.
95+
Supports: boolean, integer, string
96+
"""
97+
# Boolean - case-insensitive
98+
lower_val = value.lower()
99+
if lower_val in ("true", "1", "yes", "on"):
100+
return True
101+
if lower_val in ("false", "0", "no", "off"):
102+
return False
103+
104+
# Integer
105+
try:
106+
return int(value)
107+
except ValueError:
108+
pass
109+
110+
# String (default)
111+
return value
112+
113+
114+
class SnowSqlEnvHandler(SourceHandler):
115+
"""
116+
Handler for SnowSQL-compatible environment variables.
117+
Format: SNOWSQL_<KEY> → key
118+
Supports key mappings for SnowSQL-specific naming.
119+
120+
Key Mappings (SnowSQL → SnowCLI):
121+
- PWD → password
122+
- All other keys map directly (ACCOUNT → account, USER → user, etc.)
123+
"""
124+
125+
PREFIX = "SNOWSQL_"
126+
127+
# Key mappings from SnowSQL to SnowCLI
128+
# SnowSQL uses PWD, but SnowCLI uses PASSWORD
129+
KEY_MAPPINGS: Dict[str, str] = {
130+
"pwd": "password",
131+
}
132+
133+
@property
134+
def source_name(self) -> str:
135+
return "snowsql_env"
136+
137+
@property
138+
def priority(self) -> SourcePriority:
139+
return SourcePriority.ENVIRONMENT
140+
141+
@property
142+
def handler_type(self) -> str:
143+
return "snowsql_env"
144+
145+
def can_handle(self) -> bool:
146+
"""Check if any SNOWSQL_* env vars are set."""
147+
return any(k.startswith(self.PREFIX) for k in os.environ)
148+
149+
def discover(self, key: Optional[str] = None) -> Dict[str, ConfigValue]:
150+
"""
151+
Discover values from SNOWSQL_* environment variables.
152+
Applies key mappings for compatibility.
153+
"""
154+
values = {}
155+
156+
if key is not None:
157+
# Reverse lookup: find SnowSQL key for CLI key
158+
snowsql_key = self.get_snowsql_key(key)
159+
env_key = f"{self.PREFIX}{snowsql_key.upper()}"
160+
161+
if env_key in os.environ:
162+
raw = os.environ[env_key]
163+
values[key] = ConfigValue(
164+
key=key, # Normalized SnowCLI key
165+
value=self._parse_value(raw),
166+
source_name=self.source_name,
167+
priority=self.priority,
168+
raw_value=raw,
169+
)
170+
else:
171+
# Discover all SNOWSQL_* variables
172+
for env_key, env_value in os.environ.items():
173+
if env_key.startswith(self.PREFIX):
174+
snowsql_key = env_key[len(self.PREFIX) :].lower()
175+
# Map to SnowCLI key
176+
config_key = self.KEY_MAPPINGS.get(snowsql_key, snowsql_key)
177+
178+
values[config_key] = ConfigValue(
179+
key=config_key,
180+
value=self._parse_value(env_value),
181+
source_name=self.source_name,
182+
priority=self.priority,
183+
raw_value=env_value,
184+
)
185+
186+
return values
187+
188+
def supports_key(self, key: str) -> bool:
189+
"""Any string key can be represented as SNOWSQL_* env var."""
190+
return isinstance(key, str)
191+
192+
def get_snowsql_key(self, cli_key: str) -> str:
193+
"""Reverse mapping: CLI key → SnowSQL key."""
194+
for snowsql_key, cli_mapped_key in self.KEY_MAPPINGS.items():
195+
if cli_mapped_key == cli_key:
196+
return snowsql_key
197+
return cli_key
198+
199+
def _parse_value(self, value: str) -> Any:
200+
"""
201+
Parse string value to appropriate type.
202+
Supports: boolean, integer, string
203+
"""
204+
# Boolean - case-insensitive
205+
lower_val = value.lower()
206+
if lower_val in ("true", "1", "yes", "on"):
207+
return True
208+
if lower_val in ("false", "0", "no", "off"):
209+
return False
210+
211+
# Integer
212+
try:
213+
return int(value)
214+
except ValueError:
215+
pass
216+
217+
# String (default)
218+
return value

0 commit comments

Comments
 (0)