Skip to content

Commit 9affee5

Browse files
committed
fix: Remote feastRef FeatureStore fails first apply for a new feastProject
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
1 parent 39d408d commit 9affee5

2 files changed

Lines changed: 99 additions & 7 deletions

File tree

sdk/python/feast/registry_server.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,10 +1376,12 @@ def ApplyMaterialization(
13761376
return Empty()
13771377

13781378
def UpdateInfra(self, request: RegistryServer_pb2.UpdateInfraRequest, context):
1379-
project = self.proxied_registry.get_project(
1380-
name=request.project, allow_cache=True
1379+
# Create-or-update so first remote apply can write infra for a new project.
1380+
assert_permissions_to_update(
1381+
resource=Project(name=request.project),
1382+
getter=self.proxied_registry.get_project,
1383+
project=request.project,
13811384
)
1382-
assert_permissions(resource=project, actions=[AuthzedAction.UPDATE])
13831385
self.proxied_registry.update_infra(
13841386
infra=Infra.from_proto(request.infra),
13851387
project=request.project,
@@ -1388,10 +1390,19 @@ def UpdateInfra(self, request: RegistryServer_pb2.UpdateInfraRequest, context):
13881390
return Empty()
13891391

13901392
def GetInfra(self, request: RegistryServer_pb2.GetInfraRequest, context):
1391-
project = self.proxied_registry.get_project(
1392-
name=request.project, allow_cache=True
1393-
)
1394-
assert_permissions(resource=project, actions=[AuthzedAction.DESCRIBE])
1393+
# plan() calls get_infra before the project is created on a shared remote
1394+
# registry. Mirror ListProjectMetadata: authorize when present, and for a
1395+
# missing project require CREATE (or allow when auth is off).
1396+
try:
1397+
project = self.proxied_registry.get_project(
1398+
name=request.project, allow_cache=True
1399+
)
1400+
assert_permissions(resource=project, actions=[AuthzedAction.DESCRIBE])
1401+
except FeastObjectNotFoundException:
1402+
assert_permissions(
1403+
resource=Project(name=request.project),
1404+
actions=[AuthzedAction.CREATE],
1405+
)
13951406
return self.proxied_registry.get_infra(
13961407
project=request.project, allow_cache=request.allow_cache
13971408
).to_proto()

sdk/python/tests/unit/test_registry_server_commit_refresh.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from google.protobuf.empty_pb2 import Empty
55

66
from feast.errors import FeastPermissionError, ProjectObjectNotFoundException
7+
from feast.infra.infra_object import Infra
78
from feast.permissions.permission import AuthzedAction, Permission
89
from feast.permissions.policy import RoleBasedPolicy
910
from feast.permissions.security_manager import (
@@ -75,6 +76,8 @@ def get_project(name: str, allow_cache: bool = False):
7576
registry.get_project = Mock(side_effect=get_project)
7677
registry.refresh = Mock()
7778
registry.commit = Mock()
79+
registry.get_infra = Mock(return_value=Infra())
80+
registry.update_infra = Mock()
7881
return RegistryServer(registry=registry), registry
7982

8083

@@ -193,3 +196,81 @@ def test_commit_allows_with_only_create_permission_on_new_project(
193196

194197
assert response == Empty()
195198
registry.commit.assert_called_once()
199+
200+
201+
def test_get_infra_allows_missing_project_without_auth():
202+
no_security_manager()
203+
server, registry = _server_with_projects("existing")
204+
205+
response = server.GetInfra(
206+
RegistryServer_pb2.GetInfraRequest(project="brand-new", allow_cache=True),
207+
None,
208+
)
209+
210+
assert response == Infra().to_proto()
211+
registry.get_infra.assert_called_once_with(project="brand-new", allow_cache=True)
212+
213+
214+
def test_get_infra_allows_missing_project_with_create_permission(auth_security_manager):
215+
auth_security_manager.set_current_user(User("creator", ["team-a-creator"]))
216+
server, registry = _server_with_projects("team-b-project")
217+
218+
response = server.GetInfra(
219+
RegistryServer_pb2.GetInfraRequest(project="team-a-new", allow_cache=True),
220+
None,
221+
)
222+
223+
assert response == Infra().to_proto()
224+
registry.get_infra.assert_called_once_with(project="team-a-new", allow_cache=True)
225+
226+
227+
def test_get_infra_denies_missing_project_without_create_permission(
228+
auth_security_manager,
229+
):
230+
auth_security_manager.set_current_user(User("bob", ["team-b"]))
231+
server, _ = _server_with_projects("team-b-project")
232+
233+
with pytest.raises(FeastPermissionError):
234+
server.GetInfra(
235+
RegistryServer_pb2.GetInfraRequest(project="team-a-new", allow_cache=True),
236+
None,
237+
)
238+
239+
240+
def test_get_infra_requires_describe_for_existing_project(auth_security_manager):
241+
auth_security_manager.set_current_user(User("alice", ["team-a"]))
242+
server, registry = _server_with_projects("team-a-project", "team-b-project")
243+
244+
response = server.GetInfra(
245+
RegistryServer_pb2.GetInfraRequest(project="team-a-project", allow_cache=True),
246+
None,
247+
)
248+
assert response == Infra().to_proto()
249+
registry.get_infra.assert_called_once_with(
250+
project="team-a-project", allow_cache=True
251+
)
252+
253+
with pytest.raises(FeastPermissionError):
254+
server.GetInfra(
255+
RegistryServer_pb2.GetInfraRequest(
256+
project="team-b-project", allow_cache=True
257+
),
258+
None,
259+
)
260+
261+
262+
def test_update_infra_allows_missing_project_with_create_permission(
263+
auth_security_manager,
264+
):
265+
auth_security_manager.set_current_user(User("creator", ["team-a-creator"]))
266+
server, registry = _server_with_projects("team-b-project")
267+
268+
response = server.UpdateInfra(
269+
RegistryServer_pb2.UpdateInfraRequest(
270+
infra=Infra().to_proto(), project="team-a-new", commit=True
271+
),
272+
None,
273+
)
274+
275+
assert response == Empty()
276+
registry.update_infra.assert_called_once()

0 commit comments

Comments
 (0)