A Terraform module for creating a logical abstraction layer over Snowflake imported databases and shares. This module enables you to consolidate data from multiple sources into a unified, logical structure while preserving column comments.
This module helps you organize imported databases from different shares into your own logical layer. For example, you can collect all the product data, regardless of the original source, into one logical database and schema.
- Metadata Preservation: Automatically preserves column and table comments when creating views
- Flexible Resource Types: Create either standard views or auto-refreshing dynamic tables
- Automated Access Control: Configurable role-based access grants for database, schema, and view levels
- Multi-Source Consolidation: Map multiple source tables/views to a unified logical structure
- Existing Databases and Schemas: All source and target databases and schemas must already exist
- Snowflake Providers: Two provider configurations are required:
snowflake.sysadmin: For creating database objectssnowflake.securityadmin: For managing grants and permissions
- Preview Features: The sysadmin provider must have Python procedures enabled:
preview_features_enabled = ["snowflake_procedure_python_resource"]
- SYSADMIN role: CREATE VIEW, CREATE DYNAMIC TABLE, CREATE PROCEDURE permissions
- SECURITYADMIN role: GRANT privileges on databases, schemas, and views
module "logical_import_layer" {
source = "gitlab.dm-drogeriemarkt.com/?"
version = "1.0.0"
# Location for the stored procedure
procedure_database = "SHARED_UTILITIES"
procedure_schema = "PROCEDURES"
# Map source objects to logical layer targets
source_to_target_mappings = {
"X.SCHEMA_NAME.TABLE_V3" = {
target_database = "LOGICAL_PRODUCT_DATA"
target_schema = "MASTER_DATA"
target_name = "MATERIAL_MASTER"
}
"Y.SCHEMA_NAME.TABLE_V1" = {
target_database = "LOGICAL_PRODUCT_DATA"
target_schema = "MASTER_DATA"
target_name = "MATERIAL_UNITS"
}
"Z.PUBLIC.TABLE_DIM" = {
target_database = "LOGICAL_PRODUCT_DATA"
target_schema = "DIMENSIONS"
target_name = "CUSTOMERS"
}
}
# Grant read access to roles
database_role_grants = {
"LOGICAL_PRODUCT_DATA" = [
"DATA_ANALYST_ROLE",
"DATA_ENGINEER_ROLE",
"BI_DEVELOPER_ROLE"
]
}
providers = {
snowflake.sysadmin = snowflake.sysadmin
snowflake.securityadmin = snowflake.securityadmin
}
}module "logical_import_layer_dynamic" {
source = "gitlab.dm-drogeriemarkt.com/?"
version = "1.0.0"
procedure_database = "SHARED_UTILITIES"
procedure_schema = "PROCEDURES"
# Use dynamic tables instead of views
resource_type = "dynamic_table"
# Dynamic table configuration
dynamic_table_warehouse = "COMPUTE_WH"
dynamic_table_lag_duration = "10 minutes"
dynamic_table_lag_downstream = false
source_to_target_mappings = {
"EXTERNAL_SHARE.PROD.TRANSACTIONS" = {
target_database = "ANALYTICS"
target_schema = "STAGING"
target_name = "TRANSACTIONS_STAGED"
}
}
database_role_grants = {
"ANALYTICS" = ["ANALYST_ROLE"]
}
providers = {
snowflake.sysadmin = snowflake.sysadmin
snowflake.securityadmin = snowflake.securityadmin
}
}| Variable | Type | Description |
|---|---|---|
procedure_database |
string |
Database where the view creation stored procedure will be deployed |
procedure_schema |
string |
Schema where the view creation stored procedure will be deployed |
source_to_target_mappings |
map(object) |
Map of source objects to target logical layer objects. See structure below. |
database_role_grants |
map(list(string)) |
Map of target databases to lists of roles that should receive read access |
{
"FULLY.QUALIFIED.SOURCE" = {
target_database = "TARGET_DB"
target_schema = "TARGET_SCHEMA"
target_name = "TARGET_VIEW_NAME"
}
}| Variable | Type | Default | Description |
|---|---|---|---|
resource_type |
string |
"view" |
Type of resource to create: "view" or "dynamic_table" |
dynamic_table_warehouse |
string |
null |
Warehouse for dynamic table operations (required if resource_type = "dynamic_table") |
dynamic_table_lag_downstream |
bool |
false |
Refresh only when downstream dynamic tables are refreshed |
dynamic_table_lag_duration |
string |
null |
Maximum staleness (e.g., "60 seconds", "5 minutes", "2 hours") |
| Output | Description |
|---|---|
procedure_qualified_name |
Fully qualified name of the stored procedure |
created_views |
Map of created views with their fully qualified names |
created_dynamic_tables |
Map of created dynamic tables with their fully qualified names |
granted_databases |
List of databases that have been granted access |
granted_roles |
List of roles that have been granted access |
database_role_grants |
Map showing role-to-database access mappings |
resource_type |
Type of resources created (view or dynamic_table) |
-
Stored Procedure: Python-based Snowpark procedure that:
- Queries
INFORMATION_SCHEMAto extract column metadata - Generates
CREATE VIEWstatements with inline column comments - Preserves table-level comments from source objects
- Queries
-
Logical Layer Resources: Either views or dynamic tables created from source data
-
Access Control: Hierarchical grants providing:
- Database-level USAGE privileges
- Schema-level USAGE privileges (current and future)
- View-level SELECT privileges (current and future)
- Use descriptive, business-oriented names for target objects
- Group related data in the same target schema
- Consider using prefixes/suffixes to indicate source systems
- Follow your organization's naming conventions
Error: "procedure_database must be a non-empty string"
- Ensure both
procedure_databaseandprocedure_schemaare set - These variables are required and have no default values
Error: "dynamic_table_warehouse is required when resource_type is 'dynamic_table'"
- Set
dynamic_table_warehousewhen using dynamic tables - Example:
dynamic_table_warehouse = "COMPUTE_WH"
Error: "Cannot specify both dynamic_table_lag_downstream and dynamic_table_lag_duration"
- These settings are mutually exclusive
- Use
lag_downstream = trueORlag_duration = "10 minutes", not both
Views/Tables not created
- Verify source objects exist and are accessible
- Check that target databases and schemas exist
- Ensure the procedure database/schema exists before module execution
- Check Terraform Plan: Review the plan output to see what resources will be created
- Review Outputs: After apply, check outputs to verify created resources
- Snowflake Query History: Look for executed procedure calls and DDL statements
- Provider Configuration: Ensure both provider aliases are correctly configured
If you're using the old variable names, update your configuration:
| Old Variable | New Variable |
|---|---|
database_for_procedure |
procedure_database |
schema_for_procedure |
procedure_schema |
share_view_mapping |
source_to_target_mappings (with new structure) |
privilege_mapping |
database_role_grants |
dynamic_table_target_lag_downstream |
dynamic_table_lag_downstream |
dynamic_table_target_lag_maximum_duration |
dynamic_table_lag_duration |
Before:
share_view_mapping = {
"SOURCE_DB.SCHEMA.TABLE" = ["DEST_DB", "DEST_SCHEMA", "DEST_VIEW"]
}After:
source_to_target_mappings = {
"SOURCE_DB.SCHEMA.TABLE" = {
target_database = "DEST_DB"
target_schema = "DEST_SCHEMA"
target_name = "DEST_VIEW"
}
}When contributing to this module:
- Follow Terraform best practices and style guidelines
- Update documentation for any variable or output changes
- Add validation rules for new variables
- Test with both view and dynamic table configurations
- Update the CHANGELOG.md with your changes
This module is proprietary to dm-drogerie markt.
For issues or questions:
- Open an issue in the GitLab repository
- Contact the Tech ERP team
- Review Snowflake documentation for provider-specific questions