In this post we are going to cover Terraform for an Azure data platform. These are 7 real Terraform interview questions for data engineers, asked the way they come up on a real project, and the last one is a scenario: you must deliver a full dev data platform on Azure in one week.
A data platform on Azure is usually a lake on ADLS Gen2, a Key Vault, Data Factory and Databricks, all wired together with identities and private networking. Interviewers ask about it because this is where Terraform meets real security: keys, secrets, roles and who owns what.
Part 8 of the Real Time Terraform Interview Questions and Answers series on TechBrothersIT.
- The short answer
- Let's set up the project
- Question 1: Your manager asks for a data lake on Azure with Terraform. Which resources make up a secure ADLS Gen2 setup?
- Question 2: Secrets are pasted into tfvars files. How do you handle secrets with Key Vault and Terraform?
- Question 3: A pipeline needs to read the lake without keys. How do managed identities and role assignments work in Terraform?
- Question 4: The team wants Databricks workspaces created by Terraform. How do the azurerm and databricks providers work together?
- Question 5: A teammate asks whether Terraform should also deploy Data Factory pipelines or only the factory. Where do you draw the line?
- Question 6: Every resource has different tags and names. How do you enforce naming and tagging standards?
- Scenario 7: You must deliver a full dev data platform on Azure in one week with Terraform. What is your plan?
- The full script
The short answer
A secure lake is a storage account with the hierarchical namespace, shared keys off, public access disabled and private endpoints. Secrets flow through ephemeral values into write-only arguments, pipelines use managed identities with scoped role assignments, and the databricks provider manages what's inside the workspace. Terraform owns the Data Factory platform, not the pipelines, and locals, validation and tests enforce names and tags.
Let's set up the project
We use a small Terraform 1.17 project with real azurerm 5.11 and databricks 1.140 code for a dev platform. Nothing here is applied to Azure. We check the code with terraform validate, and with terraform test using mocked providers, so no Azure credentials are needed. Here are the starting files.
terraform {
required_version = ">= 1.17"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 5.11"
}
databricks = {
source = "databricks/databricks"
version = "~> 1.140"
}
random = {
source = "hashicorp/random"
version = "~> 3.9"
}
}
}provider "azurerm" {
features {}
storage_use_azuread = true # Entra ID, no account keys
resource_provider_registrations = "none"
resource_providers_to_register = [
"Microsoft.Storage", "Microsoft.KeyVault", "Microsoft.Sql",
"Microsoft.DataFactory", "Microsoft.Databricks",
"Microsoft.ManagedIdentity", "Microsoft.Network",
]
}
# inside the workspace: configured from the azurerm workspace
provider "databricks" {
host = azurerm_databricks_workspace.dbw.workspace_url
azure_workspace_resource_id = azurerm_databricks_workspace.dbw.id
}variable "environment" {
type = string
validation {
condition = contains(["dev", "test", "prod"], var.environment)
error_message = "environment must be one of dev, test, prod."
}
}
variable "project" {
type = string
description = "Short project code, used in every name."
validation {
condition = can(regex("^[a-z]{2,8}$", var.project))
error_message = "project must be 2 to 8 lowercase letters."
}
}variable "cost_center" {
type = string
}
variable "location" {
type = string
default = "eastus2"
}
variable "private_subnet_id" {
type = string
description = "Subnet for the private endpoints."
}
variable "private_dns_zone_ids" {
type = map(string)
description = "Private DNS zone ID for each endpoint: blob and dfs."
}
variable "dbw_vnet" {
type = object({
id = string
host_subnet = string
container_subnet = string
host_nsg_assoc = string
container_nsg_assoc = string
})
description = "Databricks VNet injection: VNet, delegated subnets, NSG associations."
}locals {
base = "${var.project}-data-${var.environment}" # sales-data-dev
names = {
rg = "rg-${local.base}"
lake = "st${var.project}data${var.environment}" # max 24
kv = "kv-${var.project}-${var.environment}"
sql = "sql-${local.base}"
adf = "adf-${local.base}"
dbw = "dbw-${local.base}"
pipeline = "id-${local.base}-pipeline"
}
tags = {
environment = var.environment
project = var.project
cost_center = var.cost_center
managed_by = "terraform"
}
}resource "azurerm_resource_group" "data" {
name = local.names.rg
location = var.location
tags = local.tags
}resource "azurerm_storage_account" "lake" {
name = local.names.lake
resource_group_name = azurerm_resource_group.data.name
location = var.location
account_tier = "Standard"
account_replication_type = "ZRS"
is_hns_enabled = true # ADLS Gen2
shared_access_key_enabled = false
public_network_access = "Disabled"
min_tls_version = "TLS1_2"
infrastructure_encryption_enabled = true
tags = local.tags
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
blob_properties {
delete_retention_policy {
days = 14
}
container_delete_retention_policy {
days = 14
}
}
}resource "azurerm_storage_data_lake_gen2_filesystem" "layer" {
for_each = toset(["landing", "raw", "curated"])
name = each.key
storage_account_id = azurerm_storage_account.lake.id
}
resource "azurerm_private_endpoint" "lake" {
for_each = toset(["blob", "dfs"])
name = "pe-${local.names.lake}-${each.key}"
resource_group_name = azurerm_resource_group.data.name
location = var.location
subnet_id = var.private_subnet_id
tags = local.tags
private_service_connection {
name = each.key
private_connection_resource_id = azurerm_storage_account.lake.id
subresource_names = [each.key]
is_manual_connection = false
}
private_dns_zone_group {
name = "privatelink-${each.key}"
private_dns_zone_ids = [var.private_dns_zone_ids[each.key]]
}
}data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "kv" {
name = local.names.kv
resource_group_name = azurerm_resource_group.data.name
location = var.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
rbac_authorization_enabled = true
purge_protection_enabled = true
soft_delete_retention_days = 30
public_network_access_enabled = false
tags = local.tags
}# generated during the run: never in Git, tfvars, plan or state
ephemeral "random_password" "sql_admin" {
length = 32
}
resource "azurerm_key_vault_secret" "sql_admin" {
name = "sql-admin-password"
key_vault_id = azurerm_key_vault.kv.id
value_wo = ephemeral.random_password.sql_admin.result
value_wo_version = 1 # bump to rotate
}
resource "azurerm_mssql_server" "meta" {
name = local.names.sql
resource_group_name = azurerm_resource_group.data.name
location = var.location
version = "12.0"
administrator_login = "sqladmin"
minimum_tls_version = "1.2"
public_network_access_enabled = false
tags = local.tags
administrator_login_password_wo_version = 1 # bump to rotate
administrator_login_password_wo = (
ephemeral.random_password.sql_admin.result
)
}resource "azurerm_user_assigned_identity" "pipeline" {
name = local.names.pipeline
resource_group_name = azurerm_resource_group.data.name
location = var.location
tags = local.tags
}
resource "azurerm_role_assignment" "pipeline_lake" {
scope = azurerm_storage_account.lake.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_user_assigned_identity.pipeline.principal_id
principal_type = "ServicePrincipal"
}resource "azurerm_data_factory" "adf" {
name = local.names.adf
resource_group_name = azurerm_resource_group.data.name
location = var.location
public_network_enabled = false
managed_virtual_network_enabled = true
tags = local.tags
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.pipeline.id]
}
dynamic "github_configuration" { # only dev is connected to Git
for_each = var.environment == "dev" ? [1] : []
content {
account_name = "sales-data-org"
repository_name = "adf-sales"
branch_name = "main"
root_folder = "/adf"
}
}
}resource "azurerm_databricks_workspace" "dbw" {
name = local.names.dbw
resource_group_name = azurerm_resource_group.data.name
location = var.location
sku = "premium" # needed for Unity Catalog
managed_resource_group_name = "rg-${local.base}-dbw-managed"
tags = local.tags
custom_parameters { # VNet injection: clusters reach the private lake
no_public_ip = true
virtual_network_id = var.dbw_vnet.id
public_subnet_name = var.dbw_vnet.host_subnet
private_subnet_name = var.dbw_vnet.container_subnet
public_subnet_network_security_group_association_id = var.dbw_vnet.host_nsg_assoc
private_subnet_network_security_group_association_id = var.dbw_vnet.container_nsg_assoc
}
}resource "azurerm_databricks_access_connector" "uc" {
name = "dbac-${local.base}"
resource_group_name = azurerm_resource_group.data.name
location = var.location
tags = local.tags
identity {
type = "SystemAssigned"
}
}
resource "azurerm_role_assignment" "uc_lake" {
scope = azurerm_storage_account.lake.id
role_definition_name = "Storage Blob Data Contributor"
principal_type = "ServicePrincipal"
principal_id = (
azurerm_databricks_access_connector.uc.identity[0].principal_id
)
}locals {
lake_dfs = "${azurerm_storage_account.lake.name}.dfs.core.windows.net"
}
resource "databricks_storage_credential" "lake" {
name = "sc-${local.base}"
azure_managed_identity {
access_connector_id = azurerm_databricks_access_connector.uc.id
}
}
resource "databricks_external_location" "curated" {
name = "curated"
url = "abfss://curated@${local.lake_dfs}/"
credential_name = databricks_storage_credential.lake.name
depends_on = [azurerm_role_assignment.uc_lake]
}# Mocked azurerm: realistic IDs, because the provider still
# validates the values that reach its arguments.
mock_data "azurerm_client_config" {
defaults = {
tenant_id = "11111111-1111-1111-1111-111111111111"
}
}
mock_resource "azurerm_resource_group" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev"
}
}
mock_resource "azurerm_storage_account" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.Storage/storageAccounts/stsalesdatadev"
}
}
mock_resource "azurerm_key_vault" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.KeyVault/vaults/kv-sales-dev"
}
}
mock_resource "azurerm_user_assigned_identity" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-sales-data-dev-pipeline"
principal_id = "22222222-2222-2222-2222-222222222222"
}
}
mock_resource "azurerm_databricks_access_connector" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.Databricks/accessConnectors/dbac-sales-data-dev"
}
}
mock_resource "azurerm_databricks_workspace" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.Databricks/workspaces/dbw-sales-data-dev"
workspace_url = "adb-1234567890123456.7.azuredatabricks.net"
}
}environment = "dev"
project = "sales"
cost_center = "cc-4410"
private_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev/subnets/snet-private-endpoints"
private_dns_zone_ids = {
blob = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net"
dfs = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/privateDnsZones/privatelink.dfs.core.windows.net"
}
dbw_vnet = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev"
host_subnet = "snet-dbw-host"
container_subnet = "snet-dbw-container"
host_nsg_assoc = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev/subnets/snet-dbw-host"
container_nsg_assoc = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev/subnets/snet-dbw-container"
}mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "lake_is_private_and_keyless" {
command = plan
assert {
condition = azurerm_storage_account.lake.is_hns_enabled
error_message = "The lake needs the hierarchical namespace."
}
assert {
condition = !azurerm_storage_account.lake.shared_access_key_enabled
error_message = "Shared keys must be off: Entra ID only."
}
assert {
condition = azurerm_storage_account.lake.public_network_access == "Disabled"
error_message = "The lake must not be reachable from the internet."
}
}mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "no_password_in_state" {
command = apply
assert {
condition = azurerm_key_vault_secret.sql_admin.value == null
error_message = "The secret value must not be stored in state."
}
assert {
condition = azurerm_mssql_server.meta.administrator_login_password == null
error_message = "The SQL password must not be stored in state."
}
}mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "pipeline_identity_gets_the_lake_only" {
command = apply
assert {
condition = azurerm_role_assignment.pipeline_lake.scope == azurerm_storage_account.lake.id
error_message = "Scope the role to the lake, not the subscription."
}
assert {
condition = azurerm_role_assignment.pipeline_lake.principal_id == azurerm_user_assigned_identity.pipeline.principal_id
error_message = "The role must go to the pipeline identity."
}
assert {
condition = contains(azurerm_data_factory.adf.identity[0].identity_ids, azurerm_user_assigned_identity.pipeline.id)
error_message = "Data Factory must run as the pipeline identity."
}
}mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "unity_catalog_points_at_the_lake" {
command = plan
assert {
condition = try(azurerm_databricks_workspace.dbw.custom_parameters[0].virtual_network_id, null) == var.dbw_vnet.id
error_message = "The workspace must be injected into our VNet."
}
assert {
condition = databricks_external_location.curated.url == "abfss://curated@stsalesdatadev.dfs.core.windows.net/"
error_message = "The external location must point at the curated layer."
}
}mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "names_follow_the_standard" {
command = plan
assert {
condition = azurerm_storage_account.lake.name == "stsalesdatadev"
error_message = "The storage account name breaks the standard."
}
assert {
condition = azurerm_data_factory.adf.name == "adf-sales-data-dev"
error_message = "The Data Factory name breaks the standard."
}
}
run "every_resource_has_a_cost_center" {
command = plan
assert {
condition = alltrue([for t in [
azurerm_resource_group.data.tags,
azurerm_storage_account.lake.tags,
azurerm_key_vault.kv.tags,
azurerm_data_factory.adf.tags,
azurerm_databricks_workspace.dbw.tags,
] : t["cost_center"] == "cc-4410"])
error_message = "Every resource needs the cost_center tag."
}
}
run "rejects_a_bad_project_code" {
command = plan
variables {
project = "Sales-Data"
}
expect_failures = [var.project]
}Question 1: Your manager asks for a data lake on Azure with Terraform. Which resources make up a secure ADLS Gen2 setup?
A resource group, a storage account with the hierarchical namespace on, a filesystem per layer, and private endpoints for blob and dfs with their private DNS zones.
The hierarchical namespace, is_hns_enabled = true, is what makes the account ADLS Gen2. In lake_layers.tf, for_each creates the landing, raw and curated filesystems and the two private endpoints.
Then we lock the account down in lake.tf: shared keys off, so only Entra ID, public_network_access = "Disabled", network rules that deny by default, TLS1_2, and soft delete for blobs and containers. Let's run terraform validate on the whole platform.
$ terraform validate
Success! The configuration is valid.Success! The configuration is valid. Now a test with a mocked azurerm provider checks the plan. We run terraform test for the lake test file.
resource "azurerm_storage_account" "lake" {
name = local.names.lake
resource_group_name = azurerm_resource_group.data.name
location = var.location
account_tier = "Standard"
account_replication_type = "ZRS"
is_hns_enabled = true # ADLS Gen2
shared_access_key_enabled = false
public_network_access = "Disabled"
min_tls_version = "TLS1_2"
infrastructure_encryption_enabled = true
tags = local.tags
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
blob_properties {
delete_retention_policy {
days = 14
}
container_delete_retention_policy {
days = 14
}
}
}$ terraform test -filter=tests/lake.tftest.hcl
tests/lake.tftest.hcl... in progress
run "lake_is_private_and_keyless"... pass
tests/lake.tftest.hcl... tearing down
tests/lake.tftest.hcl... pass
Success! 1 passed, 0 failed.run "lake_is_private_and_keyless"... pass: namespace on, shared keys off, public access disabled.
The pitfall is old examples. azurerm 5 deprecated public_network_access_enabled in favor of public_network_access. Let's paste the 4.x-style argument back in and validate.
resource "azurerm_storage_account" "lake" {
name = local.names.lake
resource_group_name = azurerm_resource_group.data.name
location = var.location
account_tier = "Standard"
account_replication_type = "ZRS"
is_hns_enabled = true # ADLS Gen2
shared_access_key_enabled = false
public_network_access_enabled = false # 4.x style
min_tls_version = "TLS1_2"
infrastructure_encryption_enabled = true
tags = local.tags
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
blob_properties {
delete_retention_policy {
days = 14
}
container_delete_retention_policy {
days = 14
}
}
}$ terraform validate
Warning: Argument is deprecated
with azurerm_storage_account.lake,
on lake.tf line 9, in resource "azurerm_storage_account" "lake":
9: public_network_access_enabled = false # 4.x style
`public_network_access_enabled` has been deprecated in favour of
`public_network_access` and will be removed in v6.0 of the AzureRM provider
Success! The configuration is valid, but there were some validation warnings
as shown above.Validate warns: Argument is deprecated, and it will be removed in v6.0 of the AzureRM provider.
With shared keys off, set storage_use_azuread = true in the provider, so Terraform uses Entra ID for the filesystems too. The identity that runs it needs a data role on the account, and a network path to it.
Question 2: Secrets are pasted into tfvars files. How do you handle secrets with Key Vault and Terraform?
Keep them out of Git, out of tfvars, and out of state.
Three rules to remember:
- Not in Git, not in .tfvars, not in state
sensitive = truehides it in the CLI output only: it is still in statedata "azurerm_key_vault_secret": the value lands in state
The pattern since Terraform 1.11 is an ephemeral random_password, created during the run and never stored. It goes into write-only arguments: value_wo on the Key Vault secret, and administrator_login_password_wo on the SQL server. Let's validate sql.tf and run the secrets test, which applies the config with a mocked azurerm and checks the state.
# generated during the run: never in Git, tfvars, plan or state
ephemeral "random_password" "sql_admin" {
length = 32
}
resource "azurerm_key_vault_secret" "sql_admin" {
name = "sql-admin-password"
key_vault_id = azurerm_key_vault.kv.id
value_wo = ephemeral.random_password.sql_admin.result
value_wo_version = 1 # bump to rotate
}
resource "azurerm_mssql_server" "meta" {
name = local.names.sql
resource_group_name = azurerm_resource_group.data.name
location = var.location
version = "12.0"
administrator_login = "sqladmin"
minimum_tls_version = "1.2"
public_network_access_enabled = false
tags = local.tags
administrator_login_password_wo_version = 1 # bump to rotate
administrator_login_password_wo = (
ephemeral.random_password.sql_admin.result
)
}$ terraform validate
Success! The configuration is valid.
$ terraform test -filter=tests/secrets.tftest.hcl
tests/secrets.tftest.hcl... in progress
run "no_password_in_state"... pass
tests/secrets.tftest.hcl... tearing down
tests/secrets.tftest.hcl... pass
Success! 1 passed, 0 failed.Validate passes, and run "no_password_in_state"... pass: neither the secret value nor the SQL password is stored.
Now let's put the ephemeral value into the normal administrator_login_password argument and validate again.
# generated during the run: never in Git, tfvars, plan or state
ephemeral "random_password" "sql_admin" {
length = 32
}
resource "azurerm_key_vault_secret" "sql_admin" {
name = "sql-admin-password"
key_vault_id = azurerm_key_vault.kv.id
value_wo = ephemeral.random_password.sql_admin.result
value_wo_version = 1 # bump to rotate
}
resource "azurerm_mssql_server" "meta" {
name = local.names.sql
resource_group_name = azurerm_resource_group.data.name
location = var.location
version = "12.0"
administrator_login = "sqladmin"
minimum_tls_version = "1.2"
public_network_access_enabled = false
tags = local.tags
administrator_login_password = (
ephemeral.random_password.sql_admin.result
)
}$ terraform validate
Error: Invalid use of ephemeral value
with azurerm_mssql_server.meta,
on sql.tf line 23, in resource "azurerm_mssql_server" "meta":
23: administrator_login_password = (
24: ephemeral.random_password.sql_admin.result
25: )
Ephemeral values are not valid for "administrator_login_password", because it
is not a write-only attribute and must be persisted to state.Validate refuses: Ephemeral values are not valid for "administrator_login_password", because it is not a write-only attribute and must be persisted to state.
Terraform can't compare a write-only value, so to rotate, you bump the version, value_wo_version or administrator_login_password_wo_version.
The vault itself uses RBAC, purge protection and no public access, and apps read the secret at runtime, with their own identity.
Question 3: A pipeline needs to read the lake without keys. How do managed identities and role assignments work in Terraform?
Create a user-assigned managed identity, attach it to Data Factory, and give it a data role on the lake with a role assignment. No keys, and no secrets to rotate.
The role assignment in identity.tf has three parts:
- The scope: the lake account,
stsalesdatadev - The role:
Storage Blob Data Contributor, because this pipeline also writes curated. For read only,Storage Blob Data Readeris enough - The principal: the
principal_idofid-sales-data-dev-pipeline
Then Data Factory runs as that identity:
# adf.tf: Data Factory runs as the pipeline identity
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.pipeline.id]
}Let's run the access test. It applies the config with a mocked provider and checks the wiring.
$ terraform test -filter=tests/access.tftest.hcl
tests/access.tftest.hcl... in progress
run "pipeline_identity_gets_the_lake_only"... pass
tests/access.tftest.hcl... tearing down
tests/access.tftest.hcl... pass
Success! 1 passed, 0 failed.run "pipeline_identity_gets_the_lake_only"... pass: the scope is the lake, the principal is the pipeline identity, and Data Factory runs as it.
Two tips. Set principal_type = "ServicePrincipal": it helps when a brand-new identity hasn't replicated yet. And the identity that runs Terraform needs permission to create role assignments on that scope.
It lives on its own, so you can grant access before the factory exists, and reuse it. Databricks gets its own: the access connector's SystemAssigned identity, with the same role on the lake.
Question 4: The team wants Databricks workspaces created by Terraform. How do the azurerm and databricks providers work together?
azurerm creates the Azure side, and the databricks provider manages what is inside the workspace.
First, azurerm in databricks.tf and access_connector.tf. The workspace is on the premium tier, which Unity Catalog needs. It is injected into our virtual network, so clusters reach the private lake. And there is an access connector, whose identity gets a data role on the lake.
Then the databricks provider is configured with host = azurerm_databricks_workspace.dbw.workspace_url and azure_workspace_resource_id = azurerm_databricks_workspace.dbw.id. In unity.tf, a storage credential uses the access connector, and an external location points at the curated filesystem. Let's run the mocked plan test.
$ terraform test -filter=tests/databricks.tftest.hcl
tests/databricks.tftest.hcl... in progress
run "unity_catalog_points_at_the_lake"... pass
tests/databricks.tftest.hcl... tearing down
tests/databricks.tftest.hcl... pass
Success! 1 passed, 0 failed.run "unity_catalog_points_at_the_lake"... pass: the workspace is in our network, and the external location points at abfss://curated@stsalesdatadev.dfs.core.windows.net/.
Provider arguments should only use values known before apply, and on a new environment the workspace doesn't exist yet. In a real project, I split it: one state for the Azure resources (workspace, access connector, roles), one for what's inside the workspace (credentials, locations, catalogs, clusters, jobs).
Account-level objects, like the network connectivity config that gives serverless compute private endpoints to the lake, need a second databricks provider, with host = "https://accounts.azuredatabricks.net" and account_id. And if Unity Catalog was enabled automatically, there is no metastore to create.
Question 5: A teammate asks whether Terraform should also deploy Data Factory pipelines or only the factory. Where do you draw the line?
Terraform owns the platform: the factory, its identity, the managed virtual network and the Git connection. Everything inside the factory, pipelines, datasets, linked services and triggers, is application code, built in ADF Studio.
azurerm could do both. Let's count the Data Factory resource types in the provider schema.
$ terraform providers schema -json > schema.json
$ jq -r '.provider_schemas[].resource_schemas | keys[]' schema.json > types.txt
$ grep -c '^azurerm_data_factory' types.txt
51
$ grep -E '^azurerm_data_factory_(pipeline|trigger_schedule|dataset_parquet)$' types.txt
azurerm_data_factory_dataset_parquet
azurerm_data_factory_pipeline
azurerm_data_factory_trigger_scheduleThere are 51 resource types that start with azurerm_data_factory, including azurerm_data_factory_pipeline, azurerm_data_factory_trigger_schedule and azurerm_data_factory_dataset_parquet.
But pipelines written in HCL are hard to build and review. So only the dev factory is connected to Git, with the dynamic "github_configuration" block in adf.tf. Test and prod get their contents from the ADF CI/CD process: publish from Git, then deploy the template.
| Object | Owner | How it is deployed |
|---|---|---|
| Factory, identity, managed VNet, Git connection | Terraform | plan and apply |
| Its role assignments, Key Vault, the lake | Terraform | plan and apply |
| Pipelines, datasets, linked services, triggers | ADF Git + CI/CD | publish, then deploy the template |
The pitfall is two owners. The published template includes the linked services, so if Terraform also manages them, each deployment undoes the other. One owner per object.
Terraform outputs the factory name and Key Vault URL, and the ADF release passes them in as parameters.
Question 6: Every resource has different tags and names. How do you enforce naming and tagging standards?
In code first. locals.tf builds every name and the tag map from a few inputs, environment, project and cost center, and every resource uses them.
Validation stops bad inputs. The project code must be 2 to 8 lowercase letters, which also keeps the storage account name within 24 characters.
Then tests prove it. Let's run the naming tests.
$ terraform test -filter=tests/naming.tftest.hcl
tests/naming.tftest.hcl... in progress
run "names_follow_the_standard"... pass
run "every_resource_has_a_cost_center"... pass
run "rejects_a_bad_project_code"... pass
tests/naming.tftest.hcl... tearing down
tests/naming.tftest.hcl... pass
Success! 3 passed, 0 failed.Success! 3 passed, 0 failed.: the names follow the standard, every resource has a cost center, and the bad project code Sales-Data is rejected.
And know the provider: azurerm has no provider-level default tags. Let's check the provider schema for any provider-level setting with tags in its name.
$ jq '[.provider_schemas[].provider.block | .attributes, .block_types | objects | keys[]] | any(test("tags"))' schema.json
falseThe check returns false. So pass the tags on every resource, or wrap resources in modules that do.
Enforce it with a policy that requires or inherits tags. If the policy adds tags of its own, use ignore_changes on just those keys, as in part 7.
Scenario 7: You must deliver a full dev data platform on Azure in one week with Terraform. What is your plan?
One layer per day, and a pipeline that checks everything before anything is applied.
- Day 1: state backend, OIDC identity, names and tags, resource providers
- Day 2: resource group, network, Databricks subnets, private DNS zones
- Day 3: lake, Key Vault, private endpoints, write-only secrets
- Day 4: Data Factory, Databricks, Unity Catalog
- Day 5: pipeline: fmt, validate, test, plan
On day 1, note that azurerm 5 registers no resource providers by default, so providers.tf sets resource_provider_registrations = "none" and lists the ones we need in resource_providers_to_register.
On day 5, every pull request runs the format check, validate and the tests. Let's run all three.
$ terraform fmt -check -recursive
$ terraform validate
Success! The configuration is valid.
$ terraform test
tests/access.tftest.hcl... in progress
run "pipeline_identity_gets_the_lake_only"... pass
tests/access.tftest.hcl... tearing down
tests/access.tftest.hcl... pass
tests/databricks.tftest.hcl... in progress
run "unity_catalog_points_at_the_lake"... pass
tests/databricks.tftest.hcl... tearing down
tests/databricks.tftest.hcl... pass
tests/lake.tftest.hcl... in progress
run "lake_is_private_and_keyless"... pass
tests/lake.tftest.hcl... tearing down
tests/lake.tftest.hcl... pass
tests/naming.tftest.hcl... in progress
run "names_follow_the_standard"... pass
run "every_resource_has_a_cost_center"... pass
run "rejects_a_bad_project_code"... pass
tests/naming.tftest.hcl... tearing down
tests/naming.tftest.hcl... pass
tests/secrets.tftest.hcl... in progress
run "no_password_in_state"... pass
tests/secrets.tftest.hcl... tearing down
tests/secrets.tftest.hcl... pass
Success! 7 passed, 0 failed.fmt -check prints nothing, validate passes, and the tests end with Success! 7 passed, 0 failed., with no Azure credentials.
Then plan and apply to dev through the pipeline, never from a laptop. The workspace contents get their own state, and on Friday I show the team a plan with no changes.
Key points to remember
- Lake: hierarchical namespace, no shared keys, no public access, private endpoints
- Secrets: ephemeral values into write-only arguments, nothing in state
- Access: managed identities + role assignments scoped to the lake
- azurerm builds the workspace, databricks manages what is inside
- Terraform owns the ADF platform; locals, validation and tests enforce names and tags
The full script
Here is a Python script that writes all the files and runs every command in order. You need Terraform 1.17 on your PATH; run it in an empty folder. It only validates and tests, so it needs no Azure credentials.
Show the full script that writes the files and runs every step
# ========================================================================
# Terraform Interview Q&A #8 - Terraform for an Azure data platform
# TechBrothersIT. Names and sample values are invented for teaching.
# Every step below was run with Terraform 1.17.0; the outputs in the video and the blog post are the real outputs.
# Providers used: hashicorp/random, null, local, time (they create nothing in any cloud). Azure and Databricks
# code in this series is checked with terraform validate only: nothing is created in a real subscription.
# To run it yourself (Linux, macOS or WSL; some steps use grep / cat):
# 1. Install Terraform 1.17 from developer.hashicorp.com/terraform/install and put it on your PATH.
# 2. python Terraform_Interview_QA_08_Code.py in an empty folder (terraform init downloads the providers).
# ========================================================================
import os, subprocess
def write(path, text):
if text is None:
if os.path.exists(path): os.remove(path)
return
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(text.strip("\n") + "\n")
def run(cmd, expect_error=False, allow_error=False, env=None):
print("\n$ " + cmd, flush=True)
rc = subprocess.run(cmd, shell=True, env=dict(os.environ, **(env or {}))).returncode
if rc != 0 and not (expect_error or allow_error):
raise SystemExit("stopped: this command was expected to succeed")
# Step 0: the starting files
write('versions.tf', """\
terraform {
required_version = ">= 1.17"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 5.11"
}
databricks = {
source = "databricks/databricks"
version = "~> 1.140"
}
random = {
source = "hashicorp/random"
version = "~> 3.9"
}
}
}
""")
write('providers.tf', """\
provider "azurerm" {
features {}
storage_use_azuread = true # Entra ID, no account keys
resource_provider_registrations = "none"
resource_providers_to_register = [
"Microsoft.Storage", "Microsoft.KeyVault", "Microsoft.Sql",
"Microsoft.DataFactory", "Microsoft.Databricks",
"Microsoft.ManagedIdentity", "Microsoft.Network",
]
}
# inside the workspace: configured from the azurerm workspace
provider "databricks" {
host = azurerm_databricks_workspace.dbw.workspace_url
azure_workspace_resource_id = azurerm_databricks_workspace.dbw.id
}
""")
write('variables.tf', """\
variable "environment" {
type = string
validation {
condition = contains(["dev", "test", "prod"], var.environment)
error_message = "environment must be one of dev, test, prod."
}
}
variable "project" {
type = string
description = "Short project code, used in every name."
validation {
condition = can(regex("^[a-z]{2,8}$", var.project))
error_message = "project must be 2 to 8 lowercase letters."
}
}
""")
write('inputs.tf', """\
variable "cost_center" {
type = string
}
variable "location" {
type = string
default = "eastus2"
}
variable "private_subnet_id" {
type = string
description = "Subnet for the private endpoints."
}
variable "private_dns_zone_ids" {
type = map(string)
description = "Private DNS zone ID for each endpoint: blob and dfs."
}
variable "dbw_vnet" {
type = object({
id = string
host_subnet = string
container_subnet = string
host_nsg_assoc = string
container_nsg_assoc = string
})
description = "Databricks VNet injection: VNet, delegated subnets, NSG associations."
}
""")
write('locals.tf', """\
locals {
base = "${var.project}-data-${var.environment}" # sales-data-dev
names = {
rg = "rg-${local.base}"
lake = "st${var.project}data${var.environment}" # max 24
kv = "kv-${var.project}-${var.environment}"
sql = "sql-${local.base}"
adf = "adf-${local.base}"
dbw = "dbw-${local.base}"
pipeline = "id-${local.base}-pipeline"
}
tags = {
environment = var.environment
project = var.project
cost_center = var.cost_center
managed_by = "terraform"
}
}
""")
write('main.tf', """\
resource "azurerm_resource_group" "data" {
name = local.names.rg
location = var.location
tags = local.tags
}
""")
write('lake.tf', """\
resource "azurerm_storage_account" "lake" {
name = local.names.lake
resource_group_name = azurerm_resource_group.data.name
location = var.location
account_tier = "Standard"
account_replication_type = "ZRS"
is_hns_enabled = true # ADLS Gen2
shared_access_key_enabled = false
public_network_access = "Disabled"
min_tls_version = "TLS1_2"
infrastructure_encryption_enabled = true
tags = local.tags
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
blob_properties {
delete_retention_policy {
days = 14
}
container_delete_retention_policy {
days = 14
}
}
}
""")
write('lake_layers.tf', """\
resource "azurerm_storage_data_lake_gen2_filesystem" "layer" {
for_each = toset(["landing", "raw", "curated"])
name = each.key
storage_account_id = azurerm_storage_account.lake.id
}
resource "azurerm_private_endpoint" "lake" {
for_each = toset(["blob", "dfs"])
name = "pe-${local.names.lake}-${each.key}"
resource_group_name = azurerm_resource_group.data.name
location = var.location
subnet_id = var.private_subnet_id
tags = local.tags
private_service_connection {
name = each.key
private_connection_resource_id = azurerm_storage_account.lake.id
subresource_names = [each.key]
is_manual_connection = false
}
private_dns_zone_group {
name = "privatelink-${each.key}"
private_dns_zone_ids = [var.private_dns_zone_ids[each.key]]
}
}
""")
write('keyvault.tf', """\
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "kv" {
name = local.names.kv
resource_group_name = azurerm_resource_group.data.name
location = var.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
rbac_authorization_enabled = true
purge_protection_enabled = true
soft_delete_retention_days = 30
public_network_access_enabled = false
tags = local.tags
}
""")
write('sql.tf', """\
# generated during the run: never in Git, tfvars, plan or state
ephemeral "random_password" "sql_admin" {
length = 32
}
resource "azurerm_key_vault_secret" "sql_admin" {
name = "sql-admin-password"
key_vault_id = azurerm_key_vault.kv.id
value_wo = ephemeral.random_password.sql_admin.result
value_wo_version = 1 # bump to rotate
}
resource "azurerm_mssql_server" "meta" {
name = local.names.sql
resource_group_name = azurerm_resource_group.data.name
location = var.location
version = "12.0"
administrator_login = "sqladmin"
minimum_tls_version = "1.2"
public_network_access_enabled = false
tags = local.tags
administrator_login_password_wo_version = 1 # bump to rotate
administrator_login_password_wo = (
ephemeral.random_password.sql_admin.result
)
}
""")
write('identity.tf', """\
resource "azurerm_user_assigned_identity" "pipeline" {
name = local.names.pipeline
resource_group_name = azurerm_resource_group.data.name
location = var.location
tags = local.tags
}
resource "azurerm_role_assignment" "pipeline_lake" {
scope = azurerm_storage_account.lake.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_user_assigned_identity.pipeline.principal_id
principal_type = "ServicePrincipal"
}
""")
write('adf.tf', """\
resource "azurerm_data_factory" "adf" {
name = local.names.adf
resource_group_name = azurerm_resource_group.data.name
location = var.location
public_network_enabled = false
managed_virtual_network_enabled = true
tags = local.tags
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.pipeline.id]
}
dynamic "github_configuration" { # only dev is connected to Git
for_each = var.environment == "dev" ? [1] : []
content {
account_name = "sales-data-org"
repository_name = "adf-sales"
branch_name = "main"
root_folder = "/adf"
}
}
}
""")
write('databricks.tf', """\
resource "azurerm_databricks_workspace" "dbw" {
name = local.names.dbw
resource_group_name = azurerm_resource_group.data.name
location = var.location
sku = "premium" # needed for Unity Catalog
managed_resource_group_name = "rg-${local.base}-dbw-managed"
tags = local.tags
custom_parameters { # VNet injection: clusters reach the private lake
no_public_ip = true
virtual_network_id = var.dbw_vnet.id
public_subnet_name = var.dbw_vnet.host_subnet
private_subnet_name = var.dbw_vnet.container_subnet
public_subnet_network_security_group_association_id = var.dbw_vnet.host_nsg_assoc
private_subnet_network_security_group_association_id = var.dbw_vnet.container_nsg_assoc
}
}
""")
write('access_connector.tf', """\
resource "azurerm_databricks_access_connector" "uc" {
name = "dbac-${local.base}"
resource_group_name = azurerm_resource_group.data.name
location = var.location
tags = local.tags
identity {
type = "SystemAssigned"
}
}
resource "azurerm_role_assignment" "uc_lake" {
scope = azurerm_storage_account.lake.id
role_definition_name = "Storage Blob Data Contributor"
principal_type = "ServicePrincipal"
principal_id = (
azurerm_databricks_access_connector.uc.identity[0].principal_id
)
}
""")
write('unity.tf', """\
locals {
lake_dfs = "${azurerm_storage_account.lake.name}.dfs.core.windows.net"
}
resource "databricks_storage_credential" "lake" {
name = "sc-${local.base}"
azure_managed_identity {
access_connector_id = azurerm_databricks_access_connector.uc.id
}
}
resource "databricks_external_location" "curated" {
name = "curated"
url = "abfss://curated@${local.lake_dfs}/"
credential_name = databricks_storage_credential.lake.name
depends_on = [azurerm_role_assignment.uc_lake]
}
""")
write('tests/mocks/azurerm.tfmock.hcl', """\
# Mocked azurerm: realistic IDs, because the provider still
# validates the values that reach its arguments.
mock_data "azurerm_client_config" {
defaults = {
tenant_id = "11111111-1111-1111-1111-111111111111"
}
}
mock_resource "azurerm_resource_group" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev"
}
}
mock_resource "azurerm_storage_account" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.Storage/storageAccounts/stsalesdatadev"
}
}
mock_resource "azurerm_key_vault" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.KeyVault/vaults/kv-sales-dev"
}
}
mock_resource "azurerm_user_assigned_identity" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-sales-data-dev-pipeline"
principal_id = "22222222-2222-2222-2222-222222222222"
}
}
mock_resource "azurerm_databricks_access_connector" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.Databricks/accessConnectors/dbac-sales-data-dev"
}
}
mock_resource "azurerm_databricks_workspace" {
defaults = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sales-data-dev/providers/Microsoft.Databricks/workspaces/dbw-sales-data-dev"
workspace_url = "adb-1234567890123456.7.azuredatabricks.net"
}
}
""")
write('tests/terraform.tfvars', """\
environment = "dev"
project = "sales"
cost_center = "cc-4410"
private_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev/subnets/snet-private-endpoints"
private_dns_zone_ids = {
blob = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net"
dfs = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/privateDnsZones/privatelink.dfs.core.windows.net"
}
dbw_vnet = {
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev"
host_subnet = "snet-dbw-host"
container_subnet = "snet-dbw-container"
host_nsg_assoc = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev/subnets/snet-dbw-host"
container_nsg_assoc = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-dev/providers/Microsoft.Network/virtualNetworks/vnet-data-dev/subnets/snet-dbw-container"
}
""")
write('tests/lake.tftest.hcl', """\
mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "lake_is_private_and_keyless" {
command = plan
assert {
condition = azurerm_storage_account.lake.is_hns_enabled
error_message = "The lake needs the hierarchical namespace."
}
assert {
condition = !azurerm_storage_account.lake.shared_access_key_enabled
error_message = "Shared keys must be off: Entra ID only."
}
assert {
condition = azurerm_storage_account.lake.public_network_access == "Disabled"
error_message = "The lake must not be reachable from the internet."
}
}
""")
write('tests/secrets.tftest.hcl', """\
mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "no_password_in_state" {
command = apply
assert {
condition = azurerm_key_vault_secret.sql_admin.value == null
error_message = "The secret value must not be stored in state."
}
assert {
condition = azurerm_mssql_server.meta.administrator_login_password == null
error_message = "The SQL password must not be stored in state."
}
}
""")
write('tests/access.tftest.hcl', """\
mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "pipeline_identity_gets_the_lake_only" {
command = apply
assert {
condition = azurerm_role_assignment.pipeline_lake.scope == azurerm_storage_account.lake.id
error_message = "Scope the role to the lake, not the subscription."
}
assert {
condition = azurerm_role_assignment.pipeline_lake.principal_id == azurerm_user_assigned_identity.pipeline.principal_id
error_message = "The role must go to the pipeline identity."
}
assert {
condition = contains(azurerm_data_factory.adf.identity[0].identity_ids, azurerm_user_assigned_identity.pipeline.id)
error_message = "Data Factory must run as the pipeline identity."
}
}
""")
write('tests/databricks.tftest.hcl', """\
mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "unity_catalog_points_at_the_lake" {
command = plan
assert {
condition = try(azurerm_databricks_workspace.dbw.custom_parameters[0].virtual_network_id, null) == var.dbw_vnet.id
error_message = "The workspace must be injected into our VNet."
}
assert {
condition = databricks_external_location.curated.url == "abfss://curated@stsalesdatadev.dfs.core.windows.net/"
error_message = "The external location must point at the curated layer."
}
}
""")
write('tests/naming.tftest.hcl', """\
mock_provider "azurerm" {
source = "./tests/mocks"
}
mock_provider "databricks" {}
run "names_follow_the_standard" {
command = plan
assert {
condition = azurerm_storage_account.lake.name == "stsalesdatadev"
error_message = "The storage account name breaks the standard."
}
assert {
condition = azurerm_data_factory.adf.name == "adf-sales-data-dev"
error_message = "The Data Factory name breaks the standard."
}
}
run "every_resource_has_a_cost_center" {
command = plan
assert {
condition = alltrue([for t in [
azurerm_resource_group.data.tags,
azurerm_storage_account.lake.tags,
azurerm_key_vault.kv.tags,
azurerm_data_factory.adf.tags,
azurerm_databricks_workspace.dbw.tags,
] : t["cost_center"] == "cc-4410"])
error_message = "Every resource needs the cost_center tag."
}
}
run "rejects_a_bad_project_code" {
command = plan
variables {
project = "Sales-Data"
}
expect_failures = [var.project]
}
""")
run('terraform fmt -check -recursive')
run('terraform init -backend=false')
# Step 1: Validate the whole dev platform against azurerm 5.11 and databricks 1.140 (no Azure calls)
run('terraform validate')
# Step 2: The same lake with a 4.x-style argument copied from an old example
write('lake.tf', """\
resource "azurerm_storage_account" "lake" {
name = local.names.lake
resource_group_name = azurerm_resource_group.data.name
location = var.location
account_tier = "Standard"
account_replication_type = "ZRS"
is_hns_enabled = true # ADLS Gen2
shared_access_key_enabled = false
public_network_access_enabled = false # 4.x style
min_tls_version = "TLS1_2"
infrastructure_encryption_enabled = true
tags = local.tags
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
blob_properties {
delete_retention_policy {
days = 14
}
container_delete_retention_policy {
days = 14
}
}
}
""")
run('terraform validate')
# Step 3: Back to the 5.x argument; a mocked test checks the lake settings in the plan
write('lake.tf', """\
resource "azurerm_storage_account" "lake" {
name = local.names.lake
resource_group_name = azurerm_resource_group.data.name
location = var.location
account_tier = "Standard"
account_replication_type = "ZRS"
is_hns_enabled = true # ADLS Gen2
shared_access_key_enabled = false
public_network_access = "Disabled"
min_tls_version = "TLS1_2"
infrastructure_encryption_enabled = true
tags = local.tags
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
blob_properties {
delete_retention_policy {
days = 14
}
container_delete_retention_policy {
days = 14
}
}
}
""")
run('terraform test -filter=tests/lake.tftest.hcl')
# Step 4: An ephemeral value in a normal argument: validate refuses
write('sql.tf', """\
# generated during the run: never in Git, tfvars, plan or state
ephemeral "random_password" "sql_admin" {
length = 32
}
resource "azurerm_key_vault_secret" "sql_admin" {
name = "sql-admin-password"
key_vault_id = azurerm_key_vault.kv.id
value_wo = ephemeral.random_password.sql_admin.result
value_wo_version = 1 # bump to rotate
}
resource "azurerm_mssql_server" "meta" {
name = local.names.sql
resource_group_name = azurerm_resource_group.data.name
location = var.location
version = "12.0"
administrator_login = "sqladmin"
minimum_tls_version = "1.2"
public_network_access_enabled = false
tags = local.tags
administrator_login_password = (
ephemeral.random_password.sql_admin.result
)
}
""")
run('terraform validate', expect_error=True) # EXPECTED TO FAIL: the video shows this error
# Step 5: Write-only arguments: validate passes, and a mocked apply finds no password in state
write('sql.tf', """\
# generated during the run: never in Git, tfvars, plan or state
ephemeral "random_password" "sql_admin" {
length = 32
}
resource "azurerm_key_vault_secret" "sql_admin" {
name = "sql-admin-password"
key_vault_id = azurerm_key_vault.kv.id
value_wo = ephemeral.random_password.sql_admin.result
value_wo_version = 1 # bump to rotate
}
resource "azurerm_mssql_server" "meta" {
name = local.names.sql
resource_group_name = azurerm_resource_group.data.name
location = var.location
version = "12.0"
administrator_login = "sqladmin"
minimum_tls_version = "1.2"
public_network_access_enabled = false
tags = local.tags
administrator_login_password_wo_version = 1 # bump to rotate
administrator_login_password_wo = (
ephemeral.random_password.sql_admin.result
)
}
""")
run('terraform validate')
run('terraform test -filter=tests/secrets.tftest.hcl')
# Step 6: A mocked apply checks the identity and role wiring
run('terraform test -filter=tests/access.tftest.hcl')
# Step 7: A mocked plan checks that Unity Catalog points at the curated layer
run('terraform test -filter=tests/databricks.tftest.hcl')
# Step 8: How many Data Factory resource types azurerm 5.11 has
run('terraform providers schema -json > schema.json')
run("jq -r '.provider_schemas[].resource_schemas | keys[]' schema.json > types.txt")
run("grep -c '^azurerm_data_factory' types.txt")
run("grep -E '^azurerm_data_factory_(pipeline|trigger_schedule|dataset_parquet)$' types.txt")
# Step 9: The naming and tagging tests
run('terraform test -filter=tests/naming.tftest.hcl')
# Step 10: Does any provider have a provider-level tags setting?
run('jq \'[.provider_schemas[].provider.block | .attributes, .block_types | objects | keys[]] | any(test("tags"))\' schema.json')
# Step 11: Day five: the pull request checks for the whole platform
run('terraform fmt -check -recursive')
run('terraform validate')
run('terraform test')
Part 9: CI/CD, Testing and Policy
The sample data on this page is invented for teaching. Every terminal output is the real output of the command above it, run with Terraform 1.17.0; IDs, timestamps and durations are masked. Azure and Databricks code is checked with terraform validate. Part of the Real Time Terraform Interview Questions and Answers series on TechBrothersIT. Features, limits and their status (GA or preview) are current as of October 2026; check the official Terraform documentation (developer.hashicorp.com/terraform) for anything you quote in an interview.



No comments:
Post a Comment
Note: Only a member of this blog may post a comment.