-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add Grant SQL Macros #5369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Grant SQL Macros #5369
Changes from 30 commits
6e88fe7
6d4b938
ea35fd1
7e2b707
898aa8f
76050da
7a3e7c6
5fb07c1
4cf705f
2fff84b
6fccef6
528dff6
95f7ae4
9079c4a
6bdb4b5
597ab05
138f443
fc7e24b
6f53b3d
b3e37cb
99b1445
7946a3b
0597398
1d263b7
c2e9aeb
0843f66
077e4ff
ab6be85
da557d9
f2c957f
eb935d5
cefcd4f
bdc0c71
67f5beb
72bdae9
794f4d1
068c59b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -657,6 +657,30 @@ def print(msg: str) -> str: | |
| print(msg) | ||
| return "" | ||
|
|
||
| @contextmember | ||
| @staticmethod | ||
| def diff_of_two_dicts(dict_a, dict_b): | ||
| """ | ||
| Given two dictionaries: | ||
| dict_a: {'key_x': ['value_1', 'value_2'], 'key_y': ['value_3']} | ||
| dict_b: {'key_x': ['value_1'], 'key_z': ['value_4']} | ||
| Return the same dictionary representation of dict_a MINUS dict_b | ||
| """ | ||
jtcohen6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| dict_diff = {} | ||
| dict_a = {k.lower(): v for k, v in dict_a.items()} | ||
| dict_b = {k.lower(): v for k, v in dict_b.items()} | ||
| for k in dict_a: | ||
| if k in dict_b: | ||
| a_lowered = map(lambda x: x.lower(), dict_a[k]) | ||
| b_lowered = map(lambda x: x.lower(), dict_b[k]) | ||
| diff = list(set(a_lowered) - set(b_lowered)) | ||
| if diff: | ||
| dict_diff.update({k: diff}) | ||
| else: | ||
| dict_diff.update({k: dict_a[k]}) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have we tested the performance on this when the grants dict config is huge (and very different from current grants)? might not suck once, but might if they have this same complicated diff on hundreds of models?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code can + should absolutely be refactored for more Pythonic comprehension + better performance / lower memory footprint. I'm not the right person to do that :)
FWIW this code will be executed in parallel |
||
| return dict_diff | ||
|
|
||
|
|
||
| def generate_base_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]: | ||
| ctx = BaseContext(cli_vars) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| {% macro copy_grants() %} | ||
| {{ return(adapter.dispatch('copy_grants', 'dbt')()) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro default__copy_grants() %} | ||
| {{ return(True) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro should_revoke(existing_relation, full_refresh_mode=True) %} | ||
|
|
||
| {% if not existing_relation %} | ||
| {#-- The table doesn't already exist, so no grants to copy over --#} | ||
| {{ return(False) }} | ||
| {% elif full_refresh_mode %} | ||
| {#-- The object is being REPLACED -- whether grants are copied over depends on the value of user config --#} | ||
| {{ return(copy_grants()) }} | ||
| {% else %} | ||
| {#-- The table is being merged/upserted/inserted -- grants will be carried over --#} | ||
| {{ return(True) }} | ||
| {% endif %} | ||
|
|
||
| {% endmacro %} | ||
|
|
||
| {% macro get_show_grant_sql(relation) %} | ||
| {{ return(adapter.dispatch("get_show_grant_sql", "dbt")(relation)) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro default__get_show_grant_sql(relation) %} | ||
| show grants on {{ relation }} {{ relation }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro get_grant_sql(relation, grant_config) %} | ||
| {{ return(adapter.dispatch('get_grant_sql', 'dbt')(relation, grant_config)) }} | ||
| {% endmacro %} | ||
|
|
||
| {%- macro default__get_grant_sql(relation, grant_config) -%} | ||
| {%- for privilege in grant_config.keys() -%} | ||
| {%- set grantees = grant_config[privilege] -%} | ||
| {%- if grantees -%} | ||
| {%- for grantee in grantees -%} | ||
| grant {{ privilege }} on {{ relation }} {{ relation }} to {{ grantee}}; | ||
| {%- endfor -%} | ||
| {%- endif -%} | ||
| {%- endfor -%} | ||
| {%- endmacro %} | ||
|
|
||
| {% macro get_revoke_sql(relation, grant_config) %} | ||
| {{ return(adapter.dispatch("get_revoke_sql", "dbt")(relation, grant_config)) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro default__get_revoke_sql(relation, grant_config) %} | ||
| {%- for privilege in grant_config.keys() -%} | ||
| {%- set grantees = grant[privilege] -%} | ||
| {%- if grantees -%} | ||
| {%- for grantee in grantees -%} | ||
| revoke {{ privilege }} on {{ relation }} {{ relation }} from {{ grantee }}; | ||
| {% endfor -%} | ||
| {%- endif -%} | ||
| {%- endfor -%} | ||
| {%- endmacro -%} | ||
|
|
||
| {% macro apply_grants(relation, grant_config, should_revoke) %} | ||
| {{ return(adapter.dispatch("apply_grants", "dbt")(relation, grant_config, should_revoke)) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro default__apply_grants(relation, grant_config, should_revoke=True) %} | ||
| {% if grant_config %} | ||
| {% if should_revoke %} | ||
| {% set current_grants_table = run_query(get_show_grant_sql(relation)) %} | ||
| {% set current_grants_dict = adapter.standardize_grants_dict(current_grants_table) %} | ||
| {% set needs_granting = diff_of_two_dicts(grant_config, current_grants_dict) %} | ||
| {% set needs_revoking = diff_of_two_dicts(current_grants_dict, grant_config) %} | ||
| {% if not (needs_granting or needs_revoking) %} | ||
| {{ log('All grants are in place, no revocation or granting needed.')}} | ||
McKnight-42 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {% endif %} | ||
| {% else %} | ||
| {% set needs_revoking = {} %} | ||
| {% set needs_granting = grant_config %} | ||
| {% endif %} | ||
| {% if needs_granting or needs_revoking %} | ||
| {% call statement('grants') %} | ||
| {{ get_revoke_sql(relation, needs_revoking) }} | ||
| {{ get_grant_sql(relation, needs_granting) }} | ||
| {% endcall %} | ||
| {% endif %} | ||
| {% endif %} | ||
| {% endmacro %} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ | |
| -- BEGIN, in a separate transaction | ||
| {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation)-%} | ||
| {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%} | ||
| -- grab current tables grants config for comparision later on | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not something needed to do before merging, but we added the same change pretty much for all materialization methods. Should we consider refactoring the code so we can only do it in one place? |
||
| {% set grant_config = config.get('grants') %} | ||
McKnight-42 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {{ drop_relation_if_exists(preexisting_intermediate_relation) }} | ||
| {{ drop_relation_if_exists(preexisting_backup_relation) }} | ||
|
|
||
|
|
@@ -59,6 +61,9 @@ | |
| {% do to_drop.append(backup_relation) %} | ||
| {% endif %} | ||
|
|
||
| {% set should_revoke = should_reovke(existing_relation, full_refresh_mode) %} | ||
| {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %} | ||
|
|
||
| {% do persist_docs(target_relation, model) %} | ||
|
|
||
| {% if existing_relation is none or existing_relation.is_view or should_full_refresh() %} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -202,3 +202,16 @@ | |
| comment on column {{ relation }}.{{ adapter.quote(column_name) if column_dict[column_name]['quote'] else column_name }} is {{ escaped_comment }}; | ||
| {% endfor %} | ||
| {% endmacro %} | ||
|
|
||
McKnight-42 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {%- macro postgres__get_show_grant_sql(relation) -%} | ||
| select grantee, privilege_type | ||
| from information_schema.role_table_grants | ||
| where grantor = current_role | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a TODO, just an open question: Should we / do we need to limit ourselves to just the grants provided by the To copy-paste what I just said in
By removing this logic, we'd be making a strong claim, that ALL the grants applied on this object, MUST be applied by dbt, in the No action needed right now. Just wanted to leave the comment for future reference. |
||
| and grantee != current_role | ||
jtcohen6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| and table_schema = '{{ relation.schema }}' | ||
| and table_name = '{{ relation.identifier }}' | ||
| {%- endmacro -%} | ||
|
|
||
| {% macro postgres__coppy_grants() %} | ||
| {{ return(False) }} | ||
| {% endmacro %} | ||
Uh oh!
There was an error while loading. Please reload this page.