Skip to content

Commit 27ea12a

Browse files
committed
Implement application_roles parameter for BUSINESS_ROLE object type
1 parent 9244f44 commit 27ea12a

File tree

11 files changed

+61
-4
lines changed

11 files changed

+61
-4
lines changed

CHANGELOG.md

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

3-
## [0.61.0]
3+
## [0.62.0] - 2026-01-13
4+
5+
- Added `application_roles` parameter form `BUSINESS_ROLE` object type. It is now possible to grant application roles directly to business roles. Env prefix is currently not supported, applications are supposed to be managed via different means for now.
6+
7+
## [0.61.0] - 2025-12-29
48

59
- Added `DECFLOAT` data type. You should never find yourself using it, but it exists nonetheless.
610
- Renamed `SNAPSHOT_POLICY`, `SNAPSHOT_SET` object types to `BACKUP_POLICY`, `BACKUP_SET`. Config paths should be updated from `snapshot_*` to `backup_*`.

snowddl/blueprint/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
Ident,
7171
AccountIdent,
7272
AccountObjectIdent,
73+
ApplicationRoleIdent,
7374
DatabaseIdent,
7475
DatabaseRoleIdent,
7576
OutboundShareIdent,
@@ -86,6 +87,7 @@
8687
build_grant_name_ident,
8788
build_future_grant_name_ident,
8889
build_default_namespace_ident,
90+
build_application_role_ident,
8991
build_share_read_ident,
9092
)
9193
from .ident_pattern import IdentPattern

snowddl/blueprint/blueprint.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
DatabaseIdent,
2121
DatabaseRoleIdent,
2222
AccountIdent,
23+
ApplicationRoleIdent,
2324
OutboundShareIdent,
2425
SchemaIdent,
2526
SchemaObjectIdent,
@@ -118,6 +119,7 @@ class BusinessRoleBlueprint(AbstractBlueprint):
118119
share_read: List[Union[Ident, DatabaseRoleIdent]] = []
119120
warehouse_usage: List[AccountObjectIdent] = []
120121
warehouse_monitor: List[AccountObjectIdent] = []
122+
application_roles: List[ApplicationRoleIdent] = []
121123
technical_roles: List[AccountObjectIdent] = []
122124
global_roles: List[Ident] = []
123125

snowddl/blueprint/ident.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ def parts_for_format(self):
106106
return [f"{self.env_prefix}{self.name}"], None
107107

108108

109+
class ApplicationRoleIdent(AbstractIdentWithPrefix):
110+
def __init__(self, env_prefix, application, name):
111+
super().__init__(env_prefix)
112+
113+
self.application = self._validate_part(application)
114+
self.name = self._validate_part(name)
115+
116+
def parts_for_format(self):
117+
return [f"{self.env_prefix}{self.application}", self.name], None
118+
119+
109120
class DatabaseIdent(AbstractIdentWithPrefix):
110121
def __init__(self, env_prefix, database):
111122
super().__init__(env_prefix)

snowddl/blueprint/ident_builder.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .ident import (
55
AbstractIdent,
66
AccountObjectIdent,
7+
ApplicationRoleIdent,
78
DatabaseIdent,
89
DatabaseRoleIdent,
910
Ident,
@@ -112,6 +113,10 @@ def build_default_namespace_ident(env_prefix, default_namespace):
112113
return DatabaseIdent(env_prefix, default_namespace)
113114

114115

116+
def build_application_role_ident(application_role_name: str) -> ApplicationRoleIdent:
117+
return ApplicationRoleIdent("", *application_role_name.split(".", 2))
118+
119+
115120
def build_share_read_ident(share_name: str) -> Union[Ident, DatabaseRoleIdent]:
116121
if "." in share_name:
117122
# Shares are global, so database roles inside shares are global as well

snowddl/blueprint/object_type.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class ObjectType(Enum):
2828
"blueprint_cls": "AlertBlueprint",
2929
}
3030

31+
# Technical object type, used for GRANTs only
32+
# There is no blueprint
33+
APPLICATION_ROLE = {
34+
"singular": "APPLICATION ROLE",
35+
"plural": "APPLICATION ROLES",
36+
}
37+
3138
AUTHENTICATION_POLICY = {
3239
"singular": "AUTHENTICATION POLICY",
3340
"plural": "AUTHENTICATION POLICIES",

snowddl/parser/business_role.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Ident,
55
IdentPattern,
66
build_role_ident,
7+
build_application_role_ident,
78
build_share_read_ident,
89
)
910
from snowddl.parser.abc_parser import AbstractParser
@@ -75,6 +76,12 @@
7576
"type": "string"
7677
}
7778
},
79+
"application_roles": {
80+
"type": "array",
81+
"items": {
82+
"type": "string"
83+
}
84+
},
7885
"tech_roles": {
7986
"type": "array",
8087
"items": {
@@ -102,8 +109,12 @@ def load_blueprints(self):
102109
self.parse_multi_entity_file("business_role", business_role_json_schema, self.process_business_role)
103110

104111
def process_business_role(self, business_role_name, business_role_params):
112+
application_roles = []
105113
technical_roles = []
106114

115+
for application_role_name in business_role_params.get("application_roles", []):
116+
application_roles.append(build_application_role_ident(application_role_name))
117+
107118
for technical_role_name in business_role_params.get("technical_roles", []) + business_role_params.get("tech_roles", []):
108119
technical_roles.append(build_role_ident(self.env_prefix, technical_role_name, self.config.TECHNICAL_ROLE_SUFFIX))
109120

@@ -119,6 +130,7 @@ def process_business_role(self, business_role_name, business_role_params):
119130
share_read=[build_share_read_ident(share_name) for share_name in business_role_params.get("share_read", [])],
120131
warehouse_usage=[AccountObjectIdent(self.env_prefix, warehouse_name) for warehouse_name in business_role_params.get("warehouse_usage", [])],
121132
warehouse_monitor=[AccountObjectIdent(self.env_prefix, warehouse_name) for warehouse_name in business_role_params.get("warehouse_monitor", [])],
133+
application_roles=application_roles,
122134
technical_roles=technical_roles,
123135
global_roles=[Ident(global_role_name) for global_role_name in business_role_params.get("global_roles", [])],
124136
comment=business_role_params.get("comment"),

snowddl/resolver/abc_role_resolver.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from snowddl.blueprint import (
55
AccountGrant,
66
AccountObjectIdent,
7+
ApplicationRoleIdent,
78
DatabaseBlueprint,
89
DatabaseRoleIdent,
910
FutureGrant,
@@ -256,7 +257,7 @@ def drop_object(self, row: dict):
256257
return ResolveResult.DROP
257258

258259
def create_grant(self, role_name, grant: Grant):
259-
if grant.privilege == "USAGE" and grant.on in (ObjectType.ROLE, ObjectType.DATABASE_ROLE):
260+
if grant.privilege == "USAGE" and grant.on in (ObjectType.ROLE, ObjectType.APPLICATION_ROLE, ObjectType.DATABASE_ROLE):
260261
self.engine.execute_safe_ddl(
261262
"GRANT {on:r} {name:i} TO ROLE {role_name:i}",
262263
{
@@ -295,7 +296,7 @@ def drop_grant(self, role_name, grant: Grant):
295296
"current_role": self.engine.context.current_role,
296297
},
297298
)
298-
elif grant.privilege == "USAGE" and grant.on in (ObjectType.ROLE, ObjectType.DATABASE_ROLE):
299+
elif grant.privilege == "USAGE" and grant.on in (ObjectType.ROLE, ObjectType.APPLICATION_ROLE, ObjectType.DATABASE_ROLE):
299300
self.engine.execute_safe_ddl(
300301
"REVOKE {on:r} {name:i} FROM ROLE {role_name:i}",
301302
{
@@ -445,6 +446,13 @@ def build_share_read_grant(self, share_name: Union[Ident, DatabaseRoleIdent]) ->
445446
name=build_role_ident(self.config.env_prefix, share_name, self.config.SHARE_ACCESS_ROLE_SUFFIX),
446447
)
447448

449+
def build_application_role_grant(self, application_role_name: ApplicationRoleIdent) -> Grant:
450+
return Grant(
451+
privilege="USAGE",
452+
on=ObjectType.APPLICATION_ROLE,
453+
name=application_role_name,
454+
)
455+
448456
def build_technical_role_grant(self, technical_role_name: AccountObjectIdent) -> Grant:
449457
return Grant(
450458
privilege="USAGE",

snowddl/resolver/business_role.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ def transform_blueprint(self, business_role_bp: BusinessRoleBlueprint) -> RoleBl
4646
for warehouse_name in business_role_bp.warehouse_monitor:
4747
grants.append(self.build_warehouse_role_grant(warehouse_name, self.config.MONITOR_ROLE_TYPE))
4848

49+
# Application roles
50+
for application_role_name in business_role_bp.application_roles:
51+
grants.append(self.build_application_role_grant(application_role_name))
52+
4953
# Technical roles
5054
for technical_role_name in business_role_bp.technical_roles:
5155
grants.append(self.build_technical_role_grant(technical_role_name))

snowddl/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.61.0"
1+
__version__ = "0.62.0"

0 commit comments

Comments
 (0)