Skip to content

Commit

Permalink
stage priv execution order
Browse files Browse the repository at this point in the history
  • Loading branch information
teej committed Dec 17, 2024
1 parent 3dd6429 commit 20ca48b
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 4 deletions.
58 changes: 58 additions & 0 deletions tests/integration/test_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from titan.client import reset_cache
from titan.enums import BlueprintScope, ResourceType
from titan.exceptions import NotADAGException
from titan.gitops import collect_blueprint_config
from titan.resources.database import public_schema_urn

Expand Down Expand Up @@ -607,3 +608,60 @@ def test_blueprint_share_custom_owner(cursor, suffix):
blueprint.apply(session, plan)
finally:
cursor.execute(f"DROP SHARE IF EXISTS {share_name}")


def test_stage_read_write_privilege_execution_order(cursor, suffix, marked_for_cleanup):
session = cursor.connection

Check warning on line 614 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L614

Added line #L614 was not covered by tests

role_name = f"STAGE_ACCESS_ROLE_{suffix}"

Check warning on line 616 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L616

Added line #L616 was not covered by tests

blueprint = Blueprint()

Check warning on line 618 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L618

Added line #L618 was not covered by tests

role = res.Role(name=role_name)
read_grant = res.Grant(priv="READ", on_stage="STATIC_DATABASE.PUBLIC.STATIC_STAGE", to=role)
write_grant = res.Grant(priv="WRITE", on_stage="STATIC_DATABASE.PUBLIC.STATIC_STAGE", to=role)

Check warning on line 622 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L620-L622

Added lines #L620 - L622 were not covered by tests

# Incorrect order of execution
read_grant.requires(write_grant)

Check warning on line 625 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L625

Added line #L625 was not covered by tests

blueprint.add(role, read_grant, write_grant)

Check warning on line 627 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L627

Added line #L627 was not covered by tests

marked_for_cleanup.append(role)

Check warning on line 629 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L629

Added line #L629 was not covered by tests

with pytest.raises(NotADAGException):
blueprint.plan(session)

Check warning on line 632 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L631-L632

Added lines #L631 - L632 were not covered by tests

blueprint = Blueprint()

Check warning on line 634 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L634

Added line #L634 was not covered by tests

role = res.Role(name=role_name)
read_grant = res.Grant(priv="READ", on_stage="STATIC_DATABASE.PUBLIC.STATIC_STAGE", to=role)
write_grant = res.Grant(priv="WRITE", on_stage="STATIC_DATABASE.PUBLIC.STATIC_STAGE", to=role)

Check warning on line 638 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L636-L638

Added lines #L636 - L638 were not covered by tests

# Implicitly ordered incorrectly
blueprint.add(role, write_grant, read_grant)

Check warning on line 641 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L641

Added line #L641 was not covered by tests

plan = blueprint.plan(session)
assert len(plan) == 3
blueprint.apply(session, plan)

Check warning on line 645 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L643-L645

Added lines #L643 - L645 were not covered by tests

blueprint = Blueprint()

Check warning on line 647 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L647

Added line #L647 was not covered by tests

read_on_all = res.GrantOnAll(

Check warning on line 649 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L649

Added line #L649 was not covered by tests
priv="READ", on_type="STAGE", in_type="SCHEMA", in_name="STATIC_DATABASE.PUBLIC", to=role_name
)
future_read = res.FutureGrant(

Check warning on line 652 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L652

Added line #L652 was not covered by tests
priv="READ", on_type="STAGE", in_type="SCHEMA", in_name="STATIC_DATABASE.PUBLIC", to=role_name
)
write_on_all = res.GrantOnAll(

Check warning on line 655 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L655

Added line #L655 was not covered by tests
priv="WRITE", on_type="STAGE", in_type="SCHEMA", in_name="STATIC_DATABASE.PUBLIC", to=role_name
)
future_write = res.FutureGrant(

Check warning on line 658 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L658

Added line #L658 was not covered by tests
priv="WRITE", on_type="STAGE", in_type="SCHEMA", in_name="STATIC_DATABASE.PUBLIC", to=role_name
)

# Implicitly ordered incorrectly
blueprint.add(future_write, future_read, write_on_all, read_on_all)

Check warning on line 663 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L663

Added line #L663 was not covered by tests

plan = blueprint.plan(session)
assert len(plan) == 4
blueprint.apply(session, plan)

Check warning on line 667 in tests/integration/test_blueprint.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_blueprint.py#L665-L667

Added lines #L665 - L667 were not covered by tests
4 changes: 2 additions & 2 deletions tests/integration/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ def test_task_lifecycle_remove_predecessor(cursor, suffix, marked_for_cleanup):


def test_database_role_grants(cursor, suffix, marked_for_cleanup):
db = res.Database(name="whatever")
role = res.DatabaseRole(name="whatever_role", database=db)
db = res.Database(name=f"TEST_DATABASE_ROLE_GRANTS_{suffix}")
role = res.DatabaseRole(name=f"TEST_DATABASE_ROLE_GRANTS_{suffix}", database=db)

Check warning on line 218 in tests/integration/test_lifecycle.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_lifecycle.py#L217-L218

Added lines #L217 - L218 were not covered by tests
grant = res.Grant(priv="USAGE", on_schema=db.public_schema.fqn, to=role)
future_grant = res.FutureGrant(priv="SELECT", on_future_tables_in=db, to=role)

Expand Down
13 changes: 13 additions & 0 deletions tests/integration/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,16 @@ def test_grant_database_role_to_database_role(cursor, suffix, marked_for_cleanup

grant = res.DatabaseRoleGrant(database_role=child, to_database_role=parent)
cursor.execute(grant.create_sql())

Check warning on line 141 in tests/integration/test_resources.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_resources.py#L140-L141

Added lines #L140 - L141 were not covered by tests


def test_snowflake_builtin_database_role_grant(cursor, suffix, marked_for_cleanup):
drg = res.DatabaseRoleGrant(database_role="SNOWFLAKE.CORTEX_USER", to_role="STATIC_ROLE")
marked_for_cleanup.append(drg)
cursor.execute(drg.create_sql())

Check warning on line 147 in tests/integration/test_resources.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_resources.py#L145-L147

Added lines #L145 - L147 were not covered by tests

dbr = res.DatabaseRole(name=f"TEST_GRANT_DATABASE_ROLE_{suffix}", database="STATIC_DATABASE")
drg = res.DatabaseRoleGrant(database_role=dbr, to_database_role="STATIC_DATABASE.STATIC_DATABASE_ROLE")
marked_for_cleanup.append(dbr)
marked_for_cleanup.append(drg)
cursor.execute(dbr.create_sql())
cursor.execute(drg.create_sql())

Check warning on line 154 in tests/integration/test_resources.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_resources.py#L149-L154

Added lines #L149 - L154 were not covered by tests
46 changes: 44 additions & 2 deletions titan/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
MissingPrivilegeException,
MissingResourceException,
NonConformingPlanException,
NotADAGException,
OrphanResourceException,
)
from .identifiers import URN, parse_identifier, parse_URN, resource_label_for_type
Expand All @@ -33,7 +34,7 @@
)
from .resource_name import ResourceName
from .resource_tags import ResourceTags
from .resources import Database, RoleGrant, Schema
from .resources import Database, FutureGrant, Grant, GrantOnAll, RoleGrant, Schema
from .resources.database import public_schema_urn
from .resources.resource import (
RESOURCE_SCOPES,
Expand Down Expand Up @@ -870,6 +871,46 @@ def _create_grandparent_refs(self) -> None:
if isinstance(resource.scope, SchemaScope):
resource.requires(resource.container.container)

def _create_stage_privilege_refs(self) -> None:
stage_grants: dict[str, list[Grant]] = {}
stage_future_grants: dict[ResourceName, list[FutureGrant]] = {}
stage_grant_on_all: dict[ResourceName, list[GrantOnAll]] = {}

for resource in _walk(self._root):
if isinstance(resource, Grant):
if resource._data.on_type == ResourceType.STAGE:
if resource._data.on not in stage_grants:
stage_grants[resource._data.on] = []
stage_grants[resource._data.on].append(resource)

Check warning on line 884 in titan/blueprint.py

View check run for this annotation

Codecov / codecov/patch

titan/blueprint.py#L882-L884

Added lines #L882 - L884 were not covered by tests
elif isinstance(resource, FutureGrant):
if resource._data.on_type == ResourceType.STAGE:
if resource._data.in_name not in stage_future_grants:
stage_future_grants[resource._data.in_name] = []
stage_future_grants[resource._data.in_name].append(resource)

Check warning on line 889 in titan/blueprint.py

View check run for this annotation

Codecov / codecov/patch

titan/blueprint.py#L886-L889

Added lines #L886 - L889 were not covered by tests
elif isinstance(resource, GrantOnAll):
if resource._data.on_type == ResourceType.STAGE:
if resource._data.in_name not in stage_grant_on_all:
stage_grant_on_all[resource._data.in_name] = []
stage_grant_on_all[resource._data.in_name].append(resource)

Check warning on line 894 in titan/blueprint.py

View check run for this annotation

Codecov / codecov/patch

titan/blueprint.py#L891-L894

Added lines #L891 - L894 were not covered by tests

def _apply_refs(stage_grants):
for stage in stage_grants.keys():
read_grants = []
write_grants = []
for grant in stage_grants[stage]:
if grant._data.priv == "READ":
read_grants.append(grant)
elif grant._data.priv == "WRITE":
write_grants.append(grant)

Check warning on line 904 in titan/blueprint.py

View check run for this annotation

Codecov / codecov/patch

titan/blueprint.py#L898-L904

Added lines #L898 - L904 were not covered by tests

for w_grant in write_grants:
for r_grant in read_grants:
w_grant.requires(r_grant)

Check warning on line 908 in titan/blueprint.py

View check run for this annotation

Codecov / codecov/patch

titan/blueprint.py#L906-L908

Added lines #L906 - L908 were not covered by tests

_apply_refs(stage_grants)
_apply_refs(stage_future_grants)
_apply_refs(stage_grant_on_all)

def _finalize_resources(self) -> None:
for resource in _walk(self._root):
resource._finalized = True
Expand All @@ -883,6 +924,7 @@ def _finalize(self, session_ctx: SessionContext) -> None:
self._create_tag_references()
self._create_ownership_refs(session_ctx)
self._create_grandparent_refs()
self._create_stage_privilege_refs()
self._finalize_resources()

def generate_manifest(self, session_ctx: SessionContext) -> Manifest:
Expand Down Expand Up @@ -1233,7 +1275,7 @@ def topological_sort(resource_set: set[T], references: set[tuple[T, T]]) -> dict
outgoing_edges[node].difference_update(empty_neighbors)
nodes.reverse()
if len(nodes) != len(resource_set):
raise Exception("Graph is not a DAG")
raise NotADAGException("Graph is not a DAG")

Check warning on line 1278 in titan/blueprint.py

View check run for this annotation

Codecov / codecov/patch

titan/blueprint.py#L1278

Added line #L1278 was not covered by tests
return {value: index for index, value in enumerate(nodes)}


Expand Down
4 changes: 4 additions & 0 deletions titan/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ class WrongEditionException(Exception):

class ResourceHasContainerException(Exception):
pass


class NotADAGException(Exception):
pass

0 comments on commit 20ca48b

Please sign in to comment.