deploy_init.sql 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. -- Copyright Materialize, Inc. and contributors. All rights reserved.
  2. --
  3. -- Licensed under the Apache License, Version 2.0 (the "License");
  4. -- you may not use this file except in compliance with the License.
  5. -- You may obtain a copy of the License in the LICENSE file at the
  6. -- root of this repository, or online at
  7. --
  8. -- http://www.apache.org/licenses/LICENSE-2.0
  9. --
  10. -- Unless required by applicable law or agreed to in writing, software
  11. -- distributed under the License is distributed on an "AS IS" BASIS,
  12. -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. -- See the License for the specific language governing permissions and
  14. -- limitations under the License.
  15. {% macro deploy_init(ignore_existing_objects=False) %}
  16. {% set current_target_name = target.name %}
  17. {% set deployment = var('deployment') %}
  18. {% set target_config = deployment[current_target_name] %}
  19. -- Check if the target-specific configuration exists
  20. {% if not target_config %}
  21. {{ exceptions.raise_compiler_error("No deployment configuration found for target " ~ current_target_name) }}
  22. {% endif %}
  23. {{ log("Creating deployment environment for target " ~ current_target_name, info=True) }}
  24. {% set clusters = target_config.get('clusters', []) %}
  25. {% set schemas = target_config.get('schemas', []) %}
  26. -- Check that all production schemas
  27. -- and clusters already exist
  28. {% for schema in schemas %}
  29. {% if not schema_exists(schema) %}
  30. {{ exceptions.raise_compiler_error("Production schema " ~ schema ~ " does not exist") }}
  31. {% endif %}
  32. {% if schema_contains_sinks(schema) %}
  33. {{ exceptions.raise_compiler_error("""
  34. Production schema " ~ schema ~ " contains sinks.
  35. Blue/green deployments require sinks to be in a dedicated schema.
  36. """) }}
  37. {% endif %}
  38. {% endfor %}
  39. {% for cluster in clusters %}
  40. {% set origin_cluster = adapter.generate_final_cluster_name(cluster, force_deploy_suffix=False) %}
  41. {% if not cluster_exists(origin_cluster) %}
  42. {{ exceptions.raise_compiler_error("Production cluster " ~ origin_cluster ~ " does not exist") }}
  43. {% endif %}
  44. {% if cluster_contains_sinks(origin_cluster) %}
  45. {{ exceptions.raise_compiler_error("""
  46. Production cluster " ~ origin_cluster ~ " contains sinks.
  47. Blue/green deployments require sinks to be in a dedicated cluster.
  48. """) }}
  49. {% endif %}
  50. {% endfor %}
  51. {{ deploy_validate_permissions(clusters, schemas) }}
  52. {% for schema in schemas %}
  53. {% set deploy_schema = schema ~ "_dbt_deploy" %}
  54. {% if schema_exists(deploy_schema) %}
  55. {{ log("Deployment schema " ~ deploy_schema ~ " already exists", info=True)}}
  56. {% set schema_empty %}
  57. SELECT count(*)
  58. FROM mz_objects
  59. JOIN mz_schemas ON mz_objects.schema_id = mz_schemas.id
  60. JOIN mz_databases ON mz_databases.id = mz_schemas.database_id
  61. WHERE mz_schemas.name = {{ dbt.string_literal(deploy_schema) }}
  62. AND mz_objects.id LIKE 'u%'
  63. AND mz_databases.name = current_database()
  64. {% endset %}
  65. {% set schema_object_count = run_query(schema_empty) %}
  66. {% if execute %}
  67. {% if schema_object_count and schema_object_count.columns[0] and schema_object_count.rows[0][0] > 0 %}
  68. {% if check_schema_ci_tag(deploy_schema) %}
  69. {{ log("Schema " ~ deploy_schema ~ " was already created for this pull request", info=True) }}
  70. {% elif ignore_existing_objects %}
  71. {{ log("[Warning] Deployment schema " ~ deploy_schema ~ " is not empty", info=True) }}
  72. {{ log("[Warning] Confirm the objects it contains are expected before deployment", info=True) }}
  73. {% else %}
  74. {{ exceptions.raise_compiler_error("""
  75. Deployment schema """ ~ deploy_schema ~ """ already exists and is not empty.
  76. This is potentially dangerous as you may end up deploying objects to production you
  77. do not intend.
  78. If you are certain the objects in this schema are supposed to exist, you can ignore this
  79. error by setting ignore_existing_objects to True.
  80. dbt run-operation create_deployment_environment --args '{ignore_existing_objects: True}'
  81. """) }}
  82. {% endif %}
  83. {% endif %}
  84. {% endif %}
  85. {% else %}
  86. {{ log("Creating deployment schema " ~ deploy_schema, info=True)}}
  87. {% set create_schema %}
  88. CREATE SCHEMA {{ deploy_schema }};
  89. {% endset %}
  90. {{ run_query(create_schema) }}
  91. {{ set_schema_ci_tag() }}
  92. {{ internal_copy_schema_default_privs(schema, deploy_schema) }}
  93. {{ internal_copy_schema_grants(schema, deploy_schema) }}
  94. {% endif %}
  95. {% endfor %}
  96. {% for cluster in clusters %}
  97. {% set origin_cluster = adapter.generate_final_cluster_name(cluster, force_deploy_suffix=False) %}
  98. {% set cluster_configuration %}
  99. SELECT
  100. c.managed,
  101. c.size,
  102. c.replication_factor,
  103. c.id AS cluster_id,
  104. c.name AS cluster_name,
  105. cs.type AS schedule_type,
  106. cs.refresh_hydration_time_estimate
  107. FROM mz_clusters c
  108. LEFT JOIN mz_internal.mz_cluster_schedules cs ON cs.cluster_id = c.id
  109. WHERE c.name = {{ dbt.string_literal(origin_cluster) }}
  110. {% endset %}
  111. {% set cluster_config_results = run_query(cluster_configuration) %}
  112. {% if execute %}
  113. {% set results = cluster_config_results.rows[0] %}
  114. {% set managed = results[0] %}
  115. {% set size = results[1] %}
  116. {% set replication_factor = results[2] %}
  117. {% set schedule_type = results[5] %}
  118. {% set refresh_hydration_time_estimate = results[6] %}
  119. {% if not managed %}
  120. {{ exceptions.raise_compiler_error("Production cluster " ~ origin_cluster ~ " is not managed") }}
  121. {% endif %}
  122. {% set deploy_cluster = create_cluster(
  123. cluster_name=cluster,
  124. size=size,
  125. replication_factor=replication_factor,
  126. schedule_type=schedule_type,
  127. refresh_hydration_time_estimate=refresh_hydration_time_estimate,
  128. ignore_existing_objects=ignore_existing_objects,
  129. force_deploy_suffix=True
  130. ) %}
  131. {{ internal_copy_cluster_grants(cluster, deploy_cluster) }}
  132. {% endif %}
  133. {% endfor %}
  134. {% endmacro %}
  135. {% macro internal_copy_schema_default_privs(from, to) %}
  136. {% set find_default_privs %}
  137. SELECT
  138. 'ALTER DEFAULT PRIVILEGES ' ||
  139. CASE
  140. WHEN object_owner = 'PUBLIC' THEN 'FOR ALL ROLES '
  141. ELSE 'FOR ROLE ' || quote_ident(object_owner) || ' '
  142. END ||
  143. 'IN SCHEMA {{ to }} ' ||
  144. 'GRANT ' || privilege_type || ' ' ||
  145. 'ON ' || object_type || 's ' ||
  146. CASE
  147. WHEN grantee = 'PUBLIC' THEN 'TO PUBLIC'
  148. ELSE 'TO ' || quote_ident(grantee)
  149. END
  150. FROM mz_internal.mz_show_default_privileges
  151. WHERE database = current_database() AND schema = {{ dbt.string_literal(from) }}
  152. AND object_owner <> 'none' AND grantee <> 'none'
  153. {% endset %}
  154. {% set alter_defaults = run_query(find_default_privs) %}
  155. {% if execute %}
  156. {% for alter in alter_defaults.rows %}
  157. {{ run_query(alter[0]) }}
  158. {% endfor %}
  159. {% endif %}
  160. {% endmacro %}
  161. {% macro internal_copy_schema_grants(from, to) %}
  162. {% set find_revokes %}
  163. WITH schema_privilege AS (
  164. SELECT mz_internal.mz_aclexplode(s.privileges).*
  165. FROM mz_schemas s
  166. JOIN mz_databases d ON s.database_id = d.id
  167. WHERE d.name = current_database()
  168. AND s.name = {{ dbt.string_literal(to) }}
  169. )
  170. SELECT 'REVOKE ' || s.privilege_type || ' ' ||
  171. 'ON SCHEMA ' || quote_ident({{ dbt.string_literal(to) }}) || ' ' ||
  172. 'FROM ' || quote_ident(grantee.name)
  173. FROM schema_privilege AS s
  174. JOIN mz_roles AS grantee ON s.grantee = grantee.id
  175. WHERE grantee.name NOT IN ('none', 'mz_system', 'mz_support', current_role)
  176. {% endset %}
  177. {% set revokes = run_query(find_revokes) %}
  178. {% if execute %}
  179. {% for revoke in revokes.rows %}
  180. {{ run_query(revoke[0]) }}
  181. {% endfor %}
  182. {% endif %}
  183. {% set find_grants %}
  184. WITH schema_privilege AS (
  185. SELECT mz_internal.mz_aclexplode(s.privileges).*
  186. FROM mz_schemas s
  187. JOIN mz_databases d ON s.database_id = d.id
  188. WHERE d.name = current_database()
  189. AND s.name = {{ dbt.string_literal(from) }}
  190. )
  191. SELECT 'GRANT ' || s.privilege_type || ' ' ||
  192. 'ON SCHEMA ' || quote_ident({{ dbt.string_literal(to) }}) || ' ' ||
  193. 'TO ' || quote_ident(grantee.name)
  194. FROM schema_privilege AS s
  195. JOIN mz_roles AS grantee ON s.grantee = grantee.id
  196. WHERE grantee.name NOT IN ('none', 'mz_system', 'mz_support', current_role)
  197. {% endset %}
  198. {% set grants = run_query(find_grants) %}
  199. {% if execute %}
  200. {% for grant in grants.rows %}
  201. {{ run_query(grant[0]) }}
  202. {% endfor %}
  203. {% endif %}
  204. {% endmacro %}
  205. {% macro internal_copy_cluster_grants(from, to) %}
  206. {% set find_revokes %}
  207. WITH cluster_privilege AS (
  208. SELECT mz_internal.mz_aclexplode(privileges).*
  209. FROM mz_clusters
  210. WHERE name = {{ dbt.string_literal(to) }}
  211. )
  212. SELECT 'REVOKE ' || c.privilege_type || ' ' ||
  213. 'ON CLUSTER ' || quote_ident({{ dbt.string_literal(to) }}) || ' ' ||
  214. 'FROM ' || quote_ident(grantee.name)
  215. FROM cluster_privilege AS c
  216. JOIN mz_roles AS grantee ON c.grantee = grantee.id
  217. WHERE grantee.name NOT IN ('none', 'mz_system', 'mz_support')
  218. AND grantee.name <> current_role
  219. {% endset %}
  220. {% set revokes = run_query(find_revokes) %}
  221. {% if execute %}
  222. {% for revoke in revokes.rows %}
  223. {{ run_query(revoke[0]) }}
  224. {% endfor %}
  225. {% endif %}
  226. {% set find_grants %}
  227. WITH cluster_privilege AS (
  228. SELECT mz_internal.mz_aclexplode(privileges).*
  229. FROM mz_clusters
  230. WHERE name = {{ dbt.string_literal(from) }}
  231. )
  232. SELECT 'GRANT ' || c.privilege_type || ' ' ||
  233. 'ON CLUSTER ' || quote_ident({{ dbt.string_literal(to) }}) || ' ' ||
  234. 'TO ' || quote_ident(grantee.name)
  235. FROM cluster_privilege AS c
  236. JOIN mz_roles AS grantee ON c.grantee = grantee.id
  237. WHERE grantee.name NOT IN ('none', 'mz_system', 'mz_support')
  238. {% endset %}
  239. {% set grants = run_query(find_grants) %}
  240. {% if execute %}
  241. {% for grant in grants.rows %}
  242. {{ run_query(grant[0]) }}
  243. {% endfor %}
  244. {% endif %}
  245. {% endmacro %}