Skip to content

Commit fab0ce0

Browse files
committed
Introduce future_grants for technical roles; Rework cross-check mechanism of grants vs configured future grants
1 parent bc1c5c1 commit fab0ce0

18 files changed

+162
-116
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [0.50.0] - 2025-05-09
4+
5+
- Introduced `future_grants` for technical roles.
6+
- Added extra validation to prevent `OWNERSHIP` privilege being assigned via technical roles.
7+
- Reworked mechanism of cross-checking existing grants vs. future grants defined in config. It no longer creates new objects in large quantities.
8+
39
## [0.49.2] - 2025-04-23
410

511
- Introduced explicit `object_type` to `source_type` mapping for `STREAM`. It should help to reduce naming inconsistency presented in output of `SHOW STREAMS` command.

snowddl/blueprint/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
)
6060
from .data_type import BaseDataType, DataType
6161
from .edition import Edition
62-
from .grant import Grant, AccountGrant, FutureGrant, GrantPattern
62+
from .grant import Grant, AccountGrant, FutureGrant, GrantPattern, FutureGrantPattern
6363

6464
from .ident import (
6565
AbstractIdent,

snowddl/blueprint/blueprint.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
SearchOptimizationItem,
1313
)
1414
from .data_type import DataType
15-
from .grant import AccountGrant, Grant, FutureGrant, GrantPattern
15+
from .grant import AccountGrant, Grant, FutureGrant, GrantPattern, FutureGrantPattern
1616
from .ident import (
1717
AbstractIdent,
1818
Ident,
@@ -435,6 +435,7 @@ class TaskBlueprint(SchemaObjectBlueprint, DependsOnMixin):
435435
class TechnicalRoleBlueprint(AbstractBlueprint):
436436
full_name: AccountObjectIdent
437437
grant_patterns: List[GrantPattern] = []
438+
future_grant_patterns: List[FutureGrantPattern] = []
438439
account_grants: List[AccountGrant] = []
439440

440441

snowddl/blueprint/grant.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Union
22

3-
from .ident import AbstractIdent, AbstractIdentWithPrefix, DatabaseIdent, SchemaIdent
3+
from .ident import AbstractIdent, AbstractIdentWithPrefix, DatabaseIdent, SchemaIdent, SchemaObjectIdent
44
from .ident_pattern import IdentPattern
55
from .object_type import ObjectType
66
from ..model import BaseModelWithConfig
@@ -37,6 +37,32 @@ class FutureGrant(BaseModelWithConfig):
3737
in_parent: ObjectType
3838
name: Union[DatabaseIdent, SchemaIdent]
3939

40+
def is_matching_grant(self, grant: Grant):
41+
if not grant.on.is_future_grant_supported:
42+
return False
43+
44+
if self.privilege != grant.privilege:
45+
return False
46+
47+
if self.on_future.singular_for_grant != grant.on.singular_for_grant:
48+
return False
49+
50+
if self.in_parent == ObjectType.DATABASE:
51+
if not isinstance(grant.name, (SchemaIdent, SchemaObjectIdent)):
52+
return False
53+
54+
if self.name != grant.name.database_full_name:
55+
return False
56+
57+
if self.in_parent == ObjectType.SCHEMA:
58+
if not isinstance(grant.name, SchemaObjectIdent):
59+
return False
60+
61+
if self.name != grant.name.schema_full_name:
62+
return False
63+
64+
return True
65+
4066

4167
class GrantPattern(BaseModelWithConfig):
4268
privilege: str
@@ -57,3 +83,10 @@ def is_matching_grant(self, grant: Grant):
5783
return False
5884

5985
return True
86+
87+
88+
class FutureGrantPattern(BaseModelWithConfig):
89+
privilege: str
90+
on_future: ObjectType
91+
in_parent: ObjectType
92+
pattern: IdentPattern

snowddl/parser/technical_role.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
from snowddl.blueprint import GrantPattern, AccountGrant, TechnicalRoleBlueprint, ObjectType, IdentPattern, build_role_ident
1+
from snowddl.blueprint import (
2+
GrantPattern,
3+
FutureGrantPattern,
4+
AccountGrant,
5+
TechnicalRoleBlueprint,
6+
ObjectType,
7+
IdentPattern,
8+
build_role_ident,
9+
)
210
from snowddl.parser.abc_parser import AbstractParser
311

412

@@ -18,11 +26,22 @@
1826
"minItems": 1
1927
}
2028
},
29+
"future_grants": {
30+
"type": "object",
31+
"additionalProperties": {
32+
"type": "array",
33+
"items": {
34+
"type": "string"
35+
},
36+
"minItems": 1
37+
}
38+
},
2139
"account_grants": {
2240
"type": "array",
2341
"items": {
2442
"type": "string"
25-
}
43+
},
44+
"minItems": 1
2645
},
2746
"comment": {
2847
"type": "string"
@@ -44,6 +63,7 @@ def load_blueprints(self):
4463

4564
def process_technical_role(self, technical_role_name, technical_role_params):
4665
grant_patterns = []
66+
future_grant_patterns = []
4767
account_grants = []
4868

4969
for definition, pattern_list in technical_role_params.get("grants", {}).items():
@@ -53,12 +73,27 @@ def process_technical_role(self, technical_role_name, technical_role_params):
5373
for pattern in pattern_list:
5474
grant_patterns.append(GrantPattern(privilege=p, on=ObjectType[on], pattern=IdentPattern(pattern)))
5575

76+
for definition, pattern_list in technical_role_params.get("future_grants", {}).items():
77+
on, privileges = definition.upper().split(":")
78+
79+
for p in privileges.split(","):
80+
for pattern in pattern_list:
81+
future_grant_patterns.append(
82+
FutureGrantPattern(
83+
privilege=p,
84+
on_future=ObjectType[on],
85+
in_parent=ObjectType.SCHEMA if ("." in pattern) else ObjectType.DATABASE,
86+
pattern=IdentPattern(pattern),
87+
)
88+
)
89+
5690
for privilege in technical_role_params.get("account_grants", []):
5791
account_grants.append(AccountGrant(privilege=privilege))
5892

5993
bp = TechnicalRoleBlueprint(
6094
full_name=build_role_ident(self.env_prefix, technical_role_name, self.config.TECHNICAL_ROLE_SUFFIX),
6195
grant_patterns=grant_patterns,
96+
future_grant_patterns=future_grant_patterns,
6297
account_grants=account_grants,
6398
comment=technical_role_params.get("comment"),
6499
)

snowddl/resolver/abc_role_resolver.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def compare_object(self, bp: RoleBlueprint, row: dict):
207207

208208
# Normal grants
209209
for existing_grant in row["grants"]:
210-
if existing_grant not in bp.grants and self.grant_to_future_grant(existing_grant) not in bp.future_grants:
210+
if existing_grant not in bp.grants and not any(fg.is_matching_grant(existing_grant) for fg in bp.future_grants):
211211
self.drop_grant(bp.full_name, existing_grant)
212212
result = ResolveResult.GRANT
213213

@@ -373,11 +373,6 @@ def apply_future_grant_to_existing_objects(self, role_name, grant: FutureGrant):
373373
},
374374
)
375375

376-
def grant_to_future_grant(self, grant: Grant):
377-
# Overloaded in Database and Schema role resolvers
378-
# Other role types are not expected to utilize furue grants
379-
return None
380-
381376
def build_database_role_grants(self, database_name_pattern: IdentPattern, role_type: str) -> List[Grant]:
382377
grants = []
383378

snowddl/resolver/database_owner_role.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
Grant,
55
RoleBlueprint,
66
SchemaBlueprint,
7-
SchemaIdent,
8-
SchemaObjectIdent,
97
build_role_ident,
108
)
119
from snowddl.resolver.abc_role_resolver import AbstractRoleResolver, ObjectType
@@ -138,17 +136,3 @@ def get_blueprint_owner_role(self, database_bp: DatabaseBlueprint):
138136
)
139137

140138
return bp
141-
142-
def grant_to_future_grant(self, grant: Grant):
143-
if not grant.on.is_future_grant_supported:
144-
return None
145-
146-
if isinstance(grant.name, (SchemaIdent, SchemaObjectIdent)):
147-
return FutureGrant(
148-
privilege=grant.privilege,
149-
on_future=grant.on,
150-
in_parent=ObjectType.DATABASE,
151-
name=grant.name.database_full_name,
152-
)
153-
154-
return None

snowddl/resolver/database_read_role.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
Grant,
55
RoleBlueprint,
66
SchemaBlueprint,
7-
SchemaIdent,
8-
SchemaObjectIdent,
97
build_role_ident,
108
)
119
from snowddl.resolver.abc_role_resolver import AbstractRoleResolver, ObjectType
@@ -89,17 +87,3 @@ def get_blueprint_read_role(self, database_bp: DatabaseBlueprint):
8987
)
9088

9189
return bp
92-
93-
def grant_to_future_grant(self, grant: Grant):
94-
if not grant.on.is_future_grant_supported:
95-
return None
96-
97-
if isinstance(grant.name, (SchemaIdent, SchemaObjectIdent)):
98-
return FutureGrant(
99-
privilege=grant.privilege,
100-
on_future=grant.on,
101-
in_parent=ObjectType.DATABASE,
102-
name=grant.name.database_full_name,
103-
)
104-
105-
return None

snowddl/resolver/database_write_role.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
Grant,
55
RoleBlueprint,
66
SchemaBlueprint,
7-
SchemaIdent,
8-
SchemaObjectIdent,
97
build_role_ident,
108
)
119
from snowddl.resolver.abc_role_resolver import AbstractRoleResolver, ObjectType
@@ -89,17 +87,3 @@ def get_blueprint_write_role(self, database_bp: DatabaseBlueprint):
8987
)
9088

9189
return bp
92-
93-
def grant_to_future_grant(self, grant: Grant):
94-
if not grant.on.is_future_grant_supported:
95-
return None
96-
97-
if isinstance(grant.name, (SchemaIdent, SchemaObjectIdent)):
98-
return FutureGrant(
99-
privilege=grant.privilege,
100-
on_future=grant.on,
101-
in_parent=ObjectType.DATABASE,
102-
name=grant.name.database_full_name,
103-
)
104-
105-
return None

snowddl/resolver/outbound_share.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def compare_object(self, bp: OutboundShareBlueprint, row: dict):
7777
result = ResolveResult.GRANT
7878

7979
for ex_grant in existing_grants:
80-
if not any (grant_pattern.is_matching_grant(ex_grant) for grant_pattern in bp.grant_patterns):
80+
if not any(grant_pattern.is_matching_grant(ex_grant) for grant_pattern in bp.grant_patterns):
8181
self.drop_grant(bp.full_name, ex_grant)
8282
result = ResolveResult.GRANT
8383

0 commit comments

Comments
 (0)