Skip to content

Commit 85739dd

Browse files
Add grant_schema_procedure_usage macros for managing procedure usage privileges
1 parent 4fe90c6 commit 85739dd

File tree

5 files changed

+166
-1
lines changed

5 files changed

+166
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Data Engineers Snowflake DataOps Utils Project Changelog
22
This file contains the changelog for the Data Engineers Snowflake DataOps Utils project, detailing updates, fixes, and enhancements made to the project over time.
33

4+
## v0.3.9 2024-06-10 - Grant Object For Procedures
5+
6+
* added macro `grant_procedure_usage` to enable the ability to grant usage of a stored procedure to a role
7+
48
## v0.3.8.5 2025-08-25 - Grant Usage to Application
59

610
* added grant usage to application for `sp_sync_` in the `grant_privileges` macro

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ Below is a catalogue of publicly supported macros grouped by domain. Internal he
6161
- `grant_schema_operate`
6262
- `grant_schema_operate_specific`
6363
- `grant_schema_ownership`
64+
- `grant_schema_procedure_usage`
65+
- `grant_schema_procedure_usage_specific`
6466
- `grant_schema_read`
6567
- `grant_schema_read_specific`
6668
- `grant_share_read`
@@ -160,6 +162,7 @@ High-level macro intent summary:
160162
- `grant_schema_read*`: Ensures read usage, SELECT/REFERENCE privileges, optional future grants.
161163
- `grant_schema_monitor*`: Grants MONITOR on tasks/pipes + schema usage.
162164
- `grant_schema_operate*`: Grants OPERATE on tasks/pipes + schema usage.
165+
- `grant_schema_procedure_usage*`: Grants USAGE on all procedures + schema usage, with future grants.
163166
- `grant_share_read*`: Manages secure view exposure to outbound shares (revokes unmanaged, grants managed).
164167
- `grant_object`: Reconciles privilege sets on specific objects (TABLE/VIEW/PROCEDURE/FUNCTION/etc).
165168
- `grant_privileges`: Environment-aware bundle orchestrator.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
{% macro grant_schema_procedure_usage(exclude_schemas, grant_roles) %}
2+
{% if flags.WHICH not in ['run','run-operation'] %}{% do log('grant_schema_procedure_usage: skip (context)', info=True) %}{% do return(none) %}{% endif %}
3+
{% set dry_run = var('grants_dry_run', false) %}
4+
{% if 'INFORMATION_SCHEMA' not in exclude_schemas %}{% do exclude_schemas.append('INFORMATION_SCHEMA') %}{% endif %}
5+
{% set include_schemas = dbt_dataengineers_utils._grants_collect_schemas(exclude_schemas) %}
6+
{% if include_schemas | length == 0 %}{% do log('grant_schema_procedure_usage: no schemas to process', info=True) %}{% do return(none) %}{% endif %}
7+
{% do log('grant_schema_procedure_usage: processing ' ~ (include_schemas | length) ~ ' schemas for roles: ' ~ (grant_roles | join(', ')), info=True) %}
8+
{% do dbt_dataengineers_utils.grant_schema_procedure_usage_specific(include_schemas, grant_roles, true, dry_run) %}
9+
{% endmacro %}
10+
11+
{% macro grant_schema_procedure_usage_specific(schemas, grant_roles, revoke_current_grants, dry_run) %}
12+
{% if flags.WHICH not in ['run','run-operation'] %}{% do return(none) %}{% endif %}
13+
{% if schemas | length == 0 or grant_roles | length == 0 %}{% do log('grant_schema_procedure_usage_specific: nothing to do', info=True) %}{% do return(none) %}{% endif %}
14+
{% set total_revokes = 0 %}
15+
{% set total_grants = 0 %}
16+
{% for schema in schemas %}
17+
{% set existing_roles = [] %}
18+
19+
{# Get list of procedures in the schema using the pattern provided #}
20+
{% set proc_query %}
21+
show procedures in schema {{ target.database }}.{{ schema }}
22+
->>
23+
select
24+
"name" as object_name,
25+
"schema_name" as routine_schema,
26+
SUBSTR(
27+
"arguments",
28+
POSITION('(' IN "arguments") + 1,
29+
POSITION(')' IN "arguments") - POSITION('(' IN "arguments") - 1
30+
) AS arguments,
31+
concat("name", '(',
32+
SUBSTR(
33+
"arguments",
34+
POSITION('(' IN "arguments") + 1,
35+
POSITION(')' IN "arguments") - POSITION('(' IN "arguments") - 1
36+
),
37+
')') as routine_name
38+
from $1
39+
where "is_builtin" = 'N'
40+
{% endset %}
41+
42+
{# First run show procedures to populate result_scan #}
43+
{% set show_proc_stmt = 'show procedures in schema ' ~ target.database ~ '.' ~ schema %}
44+
{% set _ = run_query(show_proc_stmt) %}
45+
46+
{# Now get the formatted procedure list #}
47+
{% set proc_results = run_query(proc_query) %}
48+
{% set procedures = [] %}
49+
50+
{% if execute and proc_results %}
51+
{% for row in proc_results %}
52+
{% set full_proc_name = row.routine_name %}
53+
{% do procedures.append(full_proc_name) %}
54+
{% endfor %}
55+
{% endif %}
56+
57+
{% if procedures | length == 0 %}
58+
{% do log('grant_schema_procedure_usage_specific: no procedures found in schema ' ~ schema, info=True) %}
59+
{% else %}
60+
{% do log('grant_schema_procedure_usage_specific: found ' ~ (procedures | length) ~ ' procedures in schema ' ~ schema, info=True) %}
61+
62+
{# Check existing grants for each procedure #}
63+
{% for procedure in procedures %}
64+
{% set grant_query %}
65+
show grants on procedure {{ target.database }}.{{ schema }}.{{ procedure }}
66+
{% endset %}
67+
{% set grant_results = run_query(grant_query) %}
68+
69+
{% if execute and grant_results %}
70+
{% for row in grant_results %}
71+
{% set priv = row.privilege %}{% set grantee = row.grantee_name %}
72+
{% if priv == 'USAGE' %}
73+
{% if grantee not in grant_roles %}
74+
{% if revoke_current_grants %}
75+
{% set stmt = 'revoke usage on procedure ' ~ target.database ~ '.' ~ schema ~ '.' ~ procedure ~ ' from role ' ~ grantee ~ ';' %}
76+
{% do log(stmt, info=True) %}
77+
{% if not dry_run %}{% set _ = run_query(stmt) %}{% endif %}
78+
{% set total_revokes = total_revokes + 1 %}
79+
{% endif %}
80+
{% else %}
81+
{% if grantee not in existing_roles %}
82+
{% do existing_roles.append(grantee) %}
83+
{% endif %}
84+
{% endif %}
85+
{% endif %}
86+
{% endfor %}
87+
{% endif %}
88+
{% endfor %}
89+
90+
{# Grant schema usage first #}
91+
{% for role in grant_roles %}
92+
{% set schema_stmt = 'grant usage on schema ' ~ target.database ~ '.' ~ schema ~ ' to role ' ~ role ~ ';' %}
93+
{% do log(schema_stmt, info=True) %}
94+
{% if not dry_run %}{% set _ = run_query(schema_stmt) %}{% endif %}
95+
{% set total_grants = total_grants + 1 %}
96+
{% endfor %}
97+
98+
{# Grant procedure usage #}
99+
{% for role in grant_roles %}
100+
{% set needs_grant = true %}
101+
{# Check if role already has USAGE on any procedure in this schema #}
102+
{% for procedure in procedures %}
103+
{% set grant_query %}
104+
show grants on procedure {{ target.database }}.{{ schema }}.{{ procedure }}
105+
{% endset %}
106+
{% set grant_results = run_query(grant_query) %}
107+
{% if execute and grant_results %}
108+
{% for row in grant_results %}
109+
{% if row.privilege == 'USAGE' and row.grantee_name == role %}
110+
{% set needs_grant = false %}
111+
{% break %}
112+
{% endif %}
113+
{% endfor %}
114+
{% endif %}
115+
{% if not needs_grant %}{% break %}{% endif %}
116+
{% endfor %}
117+
118+
{% if needs_grant %}
119+
{% set stmt = 'grant usage on all procedures in schema ' ~ target.database ~ '.' ~ schema ~ ' to role ' ~ role ~ ';' %}
120+
{% do log(stmt, info=True) %}
121+
{% if not dry_run %}{% set _ = run_query(stmt) %}{% endif %}
122+
{% set total_grants = total_grants + 1 %}
123+
{% endif %}
124+
{% endfor %}
125+
{% endif %}
126+
{% endfor %}
127+
{% do log('grant_schema_procedure_usage_specific summary: ' ~ total_revokes ~ ' revokes, ' ~ total_grants ~ ' grants (dry_run=' ~ dry_run ~ ')', info=True) %}
128+
{% endmacro %}

macros/grants/grants.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,33 @@ macros:
269269
- name: sample_schema
270270
type: string
271271
description: Schema to scope sample grant operations to
272+
273+
- name: grant_schema_procedure_usage
274+
description: Grant USAGE privilege on all procedures in all schemas to specified roles.
275+
docs:
276+
show: true
277+
arguments:
278+
- name: exclude_schemas
279+
type: List[string]
280+
description: List of schemas to exclude from procedure grants
281+
- name: grant_roles
282+
type: List[string]
283+
description: List of roles to grant procedure usage to
284+
285+
- name: grant_schema_procedure_usage_specific
286+
description: Grant USAGE privilege on all procedures in specific schemas to specified roles.
287+
docs:
288+
show: true
289+
arguments:
290+
- name: schemas
291+
type: List[string]
292+
description: List of schemas to include for procedure grants
293+
- name: grant_roles
294+
type: List[string]
295+
description: List of roles to grant procedure usage to
296+
- name: revoke_current_grants
297+
type: boolean
298+
description: Whether to revoke existing grants from roles not in grant_roles
299+
- name: dry_run
300+
type: boolean
301+
description: Whether to run in dry-run mode (log statements without executing)

packages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
packages:
22
- package: dbt-labs/dbt_utils
3-
version: "1.3.0"
3+
version: "1.3.1"

0 commit comments

Comments
 (0)