Skip to content

Commit ca5fe9c

Browse files
lokeshranginenitmihalac
authored andcommitted
Incorporating code review comments to parse the auth block from the f… (feast-dev#36)
* Incorporating code review comments to parse the auth block from the feature_store.yaml file. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> * Incorporating code review comments - renaming type from k8 to kubernetes. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> --------- Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Signed-off-by: Abdul Hameed <ahameed@redhat.com>
1 parent 69b60b6 commit ca5fe9c

File tree

4 files changed

+198
-0
lines changed

4 files changed

+198
-0
lines changed

sdk/python/feast/errors.py

+7
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ def __init__(self, online_store_class_name: str):
223223
)
224224

225225

226+
class FeastInvalidAuthConfigClass(Exception):
227+
def __init__(self, auth_config_class_name: str):
228+
super().__init__(
229+
f"Auth Config Class '{auth_config_class_name}' should end with the string `AuthConfig`.'"
230+
)
231+
232+
226233
class FeastInvalidBaseClass(Exception):
227234
def __init__(self, class_name: str, class_type: str):
228235
super().__init__(
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Literal
2+
3+
from feast.repo_config import FeastConfigBaseModel
4+
5+
6+
class AuthConfig(FeastConfigBaseModel):
7+
type: Literal["oidc", "kubernetes", "no_auth"] = "no_auth"
8+
9+
10+
class OidcAuthConfig(AuthConfig):
11+
auth_server_url: str
12+
client_id: str
13+
client_secret: str
14+
username: str
15+
password: str
16+
realm: str = "master"
17+
18+
19+
class NoAuthConfig(AuthConfig):
20+
pass
21+
22+
23+
class K8AuthConfig(AuthConfig):
24+
pass

sdk/python/feast/repo_config.py

+57
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from feast.errors import (
2121
FeastFeatureServerTypeInvalidError,
22+
FeastInvalidAuthConfigClass,
2223
FeastOfflineStoreInvalidName,
2324
FeastOnlineStoreInvalidName,
2425
FeastRegistryNotSetError,
@@ -86,6 +87,12 @@
8687
"local": "feast.infra.feature_servers.local_process.config.LocalFeatureServerConfig",
8788
}
8889

90+
AUTH_CONFIGS_CLASS_FOR_TYPE = {
91+
"no_auth": "feast.permissions.auth_model.NoAuthConfig",
92+
"kubernetes": "feast.permissions.auth_model.K8AuthConfig",
93+
"oidc": "feast.permissions.auth_model.OidcAuthConfig",
94+
}
95+
8996

9097
class FeastBaseModel(BaseModel):
9198
"""Feast Pydantic Configuration Class"""
@@ -167,6 +174,9 @@ class RepoConfig(FeastBaseModel):
167174
online_config: Any = Field(None, alias="online_store")
168175
""" OnlineStoreConfig: Online store configuration (optional depending on provider) """
169176

177+
auth: Any = Field(None, alias="auth")
178+
""" auth: Optional if the services needs the authentication against IDPs (optional depending on provider) """
179+
170180
offline_config: Any = Field(None, alias="offline_store")
171181
""" OfflineStoreConfig: Offline store configuration (optional depending on provider) """
172182

@@ -211,6 +221,13 @@ def __init__(self, **data: Any):
211221
self._online_store = None
212222
self.online_config = data.get("online_store", "sqlite")
213223

224+
self._auth = None
225+
if "auth" not in data:
226+
self.auth = dict()
227+
self.auth["type"] = "no_auth"
228+
else:
229+
self.auth = data.get("auth")
230+
214231
self._batch_engine = None
215232
if "batch_engine" in data:
216233
self.batch_engine_config = data["batch_engine"]
@@ -270,6 +287,20 @@ def offline_store(self):
270287
self._offline_store = self.offline_config
271288
return self._offline_store
272289

290+
@property
291+
def auth_config(self):
292+
if not self._auth:
293+
if isinstance(self.auth, Dict):
294+
self._auth = get_auth_config_from_type(self.auth.get("type"))(
295+
**self.auth
296+
)
297+
elif isinstance(self.auth, str):
298+
self._auth = get_auth_config_from_type(self.auth.get("type"))()
299+
elif self.auth:
300+
self._auth = self.auth
301+
302+
return self._auth
303+
273304
@property
274305
def online_store(self):
275306
if not self._online_store:
@@ -300,6 +331,21 @@ def batch_engine(self):
300331

301332
return self._batch_engine
302333

334+
@model_validator(mode="before")
335+
def _validate_auth_config(cls, values: Any) -> Any:
336+
if "auth" in values:
337+
allowed_auth_types = AUTH_CONFIGS_CLASS_FOR_TYPE.keys()
338+
if values["auth"].get("type") is None:
339+
raise ValueError(
340+
f"auth configuration is not having authentication type. Possible values={allowed_auth_types}"
341+
)
342+
elif values["auth"]["type"] not in allowed_auth_types:
343+
raise ValueError(
344+
f'auth configuration is having invalid authentication type={values["auth"]["type"]}. Possible '
345+
f'values={allowed_auth_types}'
346+
)
347+
return values
348+
303349
@model_validator(mode="before")
304350
def _validate_online_store_config(cls, values: Any) -> Any:
305351
# This method will validate whether the online store configurations are set correctly. This explicit validation
@@ -480,6 +526,17 @@ def get_online_config_from_type(online_store_type: str):
480526
return import_class(module_name, config_class_name, config_class_name)
481527

482528

529+
def get_auth_config_from_type(auth_config_type: str):
530+
if auth_config_type in AUTH_CONFIGS_CLASS_FOR_TYPE:
531+
auth_config_type = AUTH_CONFIGS_CLASS_FOR_TYPE[auth_config_type]
532+
elif not auth_config_type.endswith("AuthConfig"):
533+
raise FeastInvalidAuthConfigClass(auth_config_type)
534+
module_name, online_store_class_type = auth_config_type.rsplit(".", 1)
535+
config_class_name = f"{online_store_class_type}"
536+
537+
return import_class(module_name, config_class_name, config_class_name)
538+
539+
483540
def get_offline_config_from_type(offline_store_type: str):
484541
if offline_store_type in OFFLINE_STORE_CLASS_FOR_TYPE:
485542
offline_store_type = OFFLINE_STORE_CLASS_FOR_TYPE[offline_store_type]

sdk/python/tests/unit/infra/scaffolding/test_repo_config.py

+110
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Optional
55

66
from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig
7+
from feast.permissions.auth_model import K8AuthConfig, NoAuthConfig, OidcAuthConfig
78
from feast.repo_config import FeastConfigError, load_repo_config
89

910

@@ -195,3 +196,112 @@ def test_no_provider():
195196
),
196197
expect_error=None,
197198
)
199+
200+
201+
def test_auth_config():
202+
_test_config(
203+
dedent(
204+
"""
205+
project: foo
206+
auth:
207+
client_id: test_client_id
208+
client_secret: test_client_secret
209+
username: test_user_name
210+
password: test_password
211+
realm: master
212+
auth_server_url: http://localhost:8712
213+
registry: "registry.db"
214+
provider: local
215+
online_store:
216+
path: foo
217+
entity_key_serialization_version: 2
218+
"""
219+
),
220+
expect_error="not having authentication type",
221+
)
222+
223+
_test_config(
224+
dedent(
225+
"""
226+
project: foo
227+
auth:
228+
type: not_valid_auth_type
229+
client_id: test_client_id
230+
client_secret: test_client_secret
231+
username: test_user_name
232+
password: test_password
233+
realm: master
234+
auth_server_url: http://localhost:8712
235+
registry: "registry.db"
236+
provider: local
237+
online_store:
238+
path: foo
239+
entity_key_serialization_version: 2
240+
"""
241+
),
242+
expect_error="invalid authentication type=not_valid_auth_type",
243+
)
244+
245+
oidc_repo_config = _test_config(
246+
dedent(
247+
"""
248+
project: foo
249+
auth:
250+
type: oidc
251+
client_id: test_client_id
252+
client_secret: test_client_secret
253+
username: test_user_name
254+
password: test_password
255+
realm: master
256+
auth_server_url: http://localhost:8712
257+
registry: "registry.db"
258+
provider: local
259+
online_store:
260+
path: foo
261+
entity_key_serialization_version: 2
262+
"""
263+
),
264+
expect_error=None,
265+
)
266+
assert oidc_repo_config.auth["type"] == "oidc"
267+
assert isinstance(oidc_repo_config.auth_config, OidcAuthConfig)
268+
assert oidc_repo_config.auth_config.client_id == "test_client_id"
269+
assert oidc_repo_config.auth_config.client_secret == "test_client_secret"
270+
assert oidc_repo_config.auth_config.username == "test_user_name"
271+
assert oidc_repo_config.auth_config.password == "test_password"
272+
assert oidc_repo_config.auth_config.realm == "master"
273+
assert oidc_repo_config.auth_config.auth_server_url == "http://localhost:8712"
274+
275+
no_auth_repo_config = _test_config(
276+
dedent(
277+
"""
278+
project: foo
279+
registry: "registry.db"
280+
provider: local
281+
online_store:
282+
path: foo
283+
entity_key_serialization_version: 2
284+
"""
285+
),
286+
expect_error=None,
287+
)
288+
assert no_auth_repo_config.auth.get("type") == "no_auth"
289+
assert isinstance(no_auth_repo_config.auth_config, NoAuthConfig)
290+
291+
k8_repo_config = _test_config(
292+
dedent(
293+
"""
294+
auth:
295+
type: kubernetes
296+
project: foo
297+
registry: "registry.db"
298+
provider: local
299+
online_store:
300+
path: foo
301+
entity_key_serialization_version: 2
302+
"""
303+
),
304+
expect_error=None,
305+
)
306+
assert k8_repo_config.auth.get("type") == "kubernetes"
307+
assert isinstance(k8_repo_config.auth_config, K8AuthConfig)

0 commit comments

Comments
 (0)