Skip to content

Worked review 1: a network + compute module

The prompt was: “Write a reusable Terraform module for a virtual network with subnets, a network security group, and a Linux VM, then call it once per environment.” What follows is the kind of first draft an AI assistant hands back for exactly that prompt — every block is individually plausible HCL, resource types are real, most argument names are real. Read it the way the review checklist recommends: providers and versions first, then backend, then resources and lifecycle, then the module boundary itself. It has 9 real bugs; check the AI bug catalog for the general pattern behind each one. Every claim below was proven with real terraform init/validate/test against Terraform 1.16.4 and hashicorp/azurerm 5.7.0. A real terraform plan against this provider needs a real subscription and Azure CLI login this course never has — confirmed directly below — so the bugs that AWS’s sibling review proves with plan are proven here with terraform test and mock_provider "azurerm" {} instead, exactly as this course’s harness does everywhere else.

# network+compute module, as generated -- modules/network-compute/main.tf
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "this" {
name = "${var.name}-rg"
location = "East US"
}
resource "azurerm_virtual_network" "this" {
name = "${var.name}-vnet"
address_space = [var.vnet_cidr]
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
}
resource "azurerm_subnet" "this" {
count = length(var.subnet_names)
name = var.subnet_names[count.index]
resource_group_name = azurerm_resource_group.this.name
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = [cidrsubnet(count.index, var.subnet_newbits, var.vnet_cidr)]
}
resource "azurerm_network_security_group" "web" {
name = "${var.name}-web-nsg"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
security_rule {
name = "allow-https"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_network_interface" "app" {
name = "${var.name}-nic"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.this[0].id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "app" {
name = "${var.name}-vm"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
size = var.vm_size
admin_username = "azureuser"
disable_password_authentication = false
admin_password = var.admin_password
network_interface_ids = [azurerm_network_interface.app.id]
delete_os_disk_on_termination = true
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
}
# network+compute module, as generated -- modules/network-compute/variables.tf and outputs.tf
variable "name" { type = string }
variable "vnet_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "subnet_newbits" {
type = number
default = 4
}
variable "subnet_names" { type = list(string) }
variable "vm_size" {
type = string
default = "Standard_B1s"
}
variable "admin_password" {
type = string
sensitive = true
}
output "vnet_address_space" {
value = azurerm_virtual_network.this.address_space
}
output "subnet_ids" {
value = azurerm_subnet.this[*].id
}
output "nsg_id" {
value = azurerm_network_security_group.web.id
}
output "vm_id" {
value = azurerm_linux_virtual_machine.app.id
}
# network+compute module, as generated -- root main.tf, variables.tf, outputs.tf
terraform {
required_providers {
azurerm = { source = "hashicorp/azurerm", version = ">= 5.0" }
}
}
provider "azurerm" {
subscription_id = "00000000-0000-0000-0000-000000000000"
}
variable "subscription_id" {
type = string
default = "00000000-0000-0000-0000-000000000000"
}
variable "environments" {
type = list(string)
default = ["dev", "staging"]
}
variable "admin_password" {
type = string
sensitive = true
default = "P@ssword-unused-in-this-proof-1234"
}
module "app" {
source = "./modules/network-compute"
count = length(var.environments)
name = var.environments[count.index]
subnet_names = ["web", "app"]
admin_password = var.admin_password
}
output "vnet_ids" {
value = [for m in module.app : m.vnet_id]
}
output "vm_ids" {
value = [for m in module.app : m.vm_id]
}

Module: a provider block inside a reusable module, called with count

Section titled “Module: a provider block inside a reusable module, called with count”
entries/01-provider-in-module/modules/network-compute/main.tf
provider "azurerm" {
features {}
}
# entries/01-provider-in-module/main.tf -- @expect-error Module is incompatible with count, for_each, and depends_on
module "app" {
source = "./modules/network-compute"
count = length(var.environments)
}

Symptom — real, at terraform init (this fails at module-installation time, before validate runs):

$ terraform init -backend=false
Initializing modules...
- app in modules/network-compute
Error: Module is incompatible with count, for_each, and depends_on
on main.tf line 13, in module "app":
13: count = length(var.environments)
The module at module.app is a legacy module which contains its own local
provider configurations, and so calls to it may not use the count, for_each,
or depends_on arguments.
If you also control the module "./modules/network-compute", consider updating
this module to instead expect provider configurations to be passed by its
caller.

🐛 Spot the bug

resource "azurerm_linux_virtual_machine" "app" {
# ...
}
module "app" {
source = "./modules/network-compute"
count = length(var.environments)
}
Show the answer

Same class of failure as AI bug catalog entry 3, provider-agnostic — Terraform core rejects count/for_each/depends_on on any module call whose module owns a local provider block, regardless of which cloud that provider targets. The fix: delete the provider block from the module entirely and let the caller’s own root-level azurerm configuration apply to every resource inside it.

Module: cidrsubnet() called with its arguments in the wrong order

Section titled “Module: cidrsubnet() called with its arguments in the wrong order”
# excerpt, as generated -- entries/02-cidrsubnet-swapped/main.tf
resource "azurerm_subnet" "this" {
count = length(var.subnet_names)
address_prefixes = [cidrsubnet(count.index, var.subnet_newbits, var.vnet_cidr)]
}

Same swap as the sibling AWS review: cidrsubnet(prefix, newbits, netnum) needs the CIDR string first and the index last; this has them reversed.

Symptom: terraform validate passes — count.index type-converts to a string without complaint. Since a real plan against azurerm needs a subscription this course never has, the failure is proven instead with a real terraform test using mock_provider "azurerm" {} and command = plan, which evaluates the same function call fully offline:

$ terraform test
run "cidrsubnet_args_swapped"... fail
Error: Invalid function argument
on modules/network-compute/main.tf line 19, in resource "azurerm_subnet" "this":
19: address_prefixes = [cidrsubnet(count.index, var.subnet_newbits, var.vnet_cidr)]
├────────────────
│ var.vnet_cidr is "10.0.0.0/16"
Invalid value for "netnum" parameter: a number is required.

🐛 Spot the bug

address_prefixes = [cidrsubnet(count.index, var.subnet_newbits, var.vnet_cidr)]
Show the answer

Identical mechanics to the sibling AWS review’s entry 2 — cidrsubnet is a core Terraform function, not an azurerm-specific one, so the same argument-order trap applies to any cloud’s subnet carving. The fix: cidrsubnet(var.vnet_cidr, var.subnet_newbits, count.index).

Module → root: an output referencing an attribute the module never exports

Section titled “Module → root: an output referencing an attribute the module never exports”
# entries/03-wrong-output-attr/outputs.tf -- @expect-error This object does not have an attribute named "vnet_id"
output "vnet_ids" {
value = [for m in module.app : m.vnet_id]
}

The module’s own outputs.tf only defines vnet_address_space, subnet_ids, nsg_id, and vm_id — never a plain vnet_id.

Symptom — real terraform test, mock_provider "azurerm" {}, command = apply, after fixing the two bugs above:

$ terraform test
run "wrong_output_attr"... fail
Error: Unsupported attribute
on outputs.tf line 2, in output "vnet_ids":
2: value = [for m in module.app : m.vnet_id]
This object does not have an attribute named "vnet_id".

🐛 Spot the bug

output "vnet_ids" {
value = [for m in module.app : m.vnet_id]
}
Show the answer

Same shape as the sibling AWS review’s entry 3 — the module was only ever asked to export the VNet’s address_space, never a plain ID. Fix: add a real output "vnet_id" { value = azurerm_virtual_network.this.id } to the module, and reference m.vnet_id from the root now that it exists.

Root: no features {} block in the provider configuration

Section titled “Root: no features {} block in the provider configuration”
# entries/04-no-features-block/main.tf -- @expect-error The argument "features" is required, but no definition was found.
terraform {
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 5.0" }
}
}
provider "azurerm" {
subscription_id = "00000000-0000-0000-0000-000000000000"
}
resource "azurerm_resource_group" "rg" {
name = "rg"
location = "East US"
}

Per the azurerm provider docs, the features {} block is required on every provider "azurerm" configuration, even empty.

Symptom — real terraform validate (a provider block with at least one real resource present forces validate to fully configure the provider and catch this; an isolated provider block with zero resources declared does not, and only fails once plan runs — confirmed separately):

$ terraform validate
Error: Missing required argument
with provider["registry.terraform.io/hashicorp/azurerm"],
on main.tf line 8, in provider "azurerm":
8: provider "azurerm" {
The argument "features" is required, but no definition was found.

🐛 Spot the bug

provider "azurerm" {
subscription_id = "00000000-0000-0000-0000-000000000000"
}
Show the answer

This is azurerm’s own version of the same “not everything is caught the same way” theme as bug 2: run against a bare provider block with zero resources declared, terraform validate passes and only plan catches the missing features {} — proven separately, isolated, with exactly that “Missing required argument” error at plan. Once real resources exist in the configuration (as they do here), validate itself has to configure the provider to check them, and catches it at validate instead. The fix is always the same: features {} on every provider "azurerm" block, empty or not.

Module: a delete_os_disk_on_termination argument carried over from the classic azurerm_virtual_machine resource

Section titled “Module: a delete_os_disk_on_termination argument carried over from the classic azurerm_virtual_machine resource”
# entries/05-hallucinated-argument/main.tf -- @expect-error An argument named "delete_os_disk_on_termination" is not expected here.
resource "azurerm_linux_virtual_machine" "app" {
# ...
delete_os_disk_on_termination = true
}

Symptom — real terraform validate:

Error: Unsupported argument
on modules/network-compute/main.tf line 61, in resource "azurerm_linux_virtual_machine" "app":
61: delete_os_disk_on_termination = true
An argument named "delete_os_disk_on_termination" is not expected here.

🐛 Spot the bug

delete_os_disk_on_termination = true
Show the answer

delete_os_disk_on_termination is a real Terraform azurerm provider argument — on the older, classic azurerm_virtual_machine resource. It was never an argument on azurerm_linux_virtual_machine (the newer, dedicated Linux VM resource this module actually uses), which manages OS disk lifecycle through its own os_disk block instead. Same class of bug as the AWS review’s deletion_protection-on-aws_instance entry: a real argument, hallucinated onto the wrong resource type because both are conceptually “a VM.” Fix: delete the argument; the os_disk block already controls disk behavior on this resource type.

entries/06-unpinned-provider/main.tf
terraform {
required_providers {
azurerm = { source = "hashicorp/azurerm", version = ">= 5.0" }
}
}

Symptom — real .terraform.lock.hcl after terraform init:

provider "registry.terraform.io/hashicorp/azurerm" {
version = "5.7.0"
constraints = ">= 5.0.0"
}

Same mechanics as AI bug catalog entry 1 — >= 5.0 resolved to whatever the registry’s newest matching release is today, with nothing promising the next init -upgrade resolves inside the same major version. Fix: version = "~> 5.0".

Resources: count on a list of subnet names

Section titled “Resources: count on a list of subnet names”
# excerpt, as generated -- entries/07-count-index-shift/main.tf
resource "azurerm_subnet" "this" {
count = length(var.subnet_names)
name = var.subnet_names[count.index]
}

Symptom — real, proven with an applied terraform_data standing in for the indexed resource (identical count.index mechanics apply to azurerm_subnet; a real Azure apply needs real credentials this course never uses), three subnet names applied, then the middle one removed:

$ terraform apply -auto-approve # subnet_names = ["web", "app", "data"]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
$ # subnet_names -> ["web", "data"]
$ terraform plan
# terraform_data.subnet[1] will be updated in-place
# terraform_data.subnet[2] will be destroyed
# (because index [2] is out of range for count)
Plan: 0 to add, 1 to change, 1 to destroy.

Same mechanics as AI bug catalog entry 9 (index shift) — count.index is positional; removing the middle subnet name shifts "data" from index 2 to index 1, which Terraform reads as “index 1 changed” plus “index 2 destroyed,” not “one subnet removed, two untouched.” Fix: for_each = toset(var.subnet_names), keyed by name.

Resources: no lifecycle block on the one VM that matters

Section titled “Resources: no lifecycle block on the one VM that matters”
# excerpt, as generated -- entries/08-no-lifecycle/main.tf
resource "azurerm_linux_virtual_machine" "app" {
# ...
# no lifecycle block at all
}

Symptom — real, proven with an applied terraform_data standing in for the VM:

$ terraform apply -auto-approve
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ terraform plan -destroy
Plan: 0 to add, 0 to change, 1 to destroy.

Adding lifecycle { prevent_destroy = true } and repeating the identical plan -destroy:

Plan: 0 to add, 0 to change, 1 to destroy.
Error: Instance cannot be destroyed
on main.tf line 1:
1: resource "terraform_data" "vm" {
Resource terraform_data.vm has lifecycle.prevent_destroy set, but the plan
calls for this resource to be destroyed. To avoid this error and continue
with the plan, either disable lifecycle.prevent_destroy or reduce the scope
of the plan using the -target option.

Same mechanics as AI bug catalog entry 8 — prevent_destroy protects only the resource it’s declared on, so a VM without it destroys with zero resistance from any stray -destroy, removed module block, or unmoved rename.

Hardcoded location with no way to override it, and a decoy subscription_id variable

Section titled “Hardcoded location with no way to override it, and a decoy subscription_id variable”
entries/09-hardcoded-region-and-subscription/excerpt
resource "azurerm_resource_group" "this" {
location = "East US"
}
entries/09-hardcoded-region-and-subscription/excerpt-root
provider "azurerm" {
subscription_id = "00000000-0000-0000-0000-000000000000"
}
variable "subscription_id" {
type = string
default = "00000000-0000-0000-0000-000000000000"
}

Symptom — real, on the actual generated files:

$ grep -n "var.location" modules/network-compute/main.tf modules/network-compute/variables.tf
(no output -- there is no location variable declared at all, in the module or the root)
$ grep -n "subscription_id" main.tf variables.tf
main.tf:9: subscription_id = "00000000-0000-0000-0000-000000000000"
variables.tf:1:variable "subscription_id" {

Two related but distinct bugs: the module’s location is a bare literal with no variable at all backing it — every environment this module is called for (dev, staging, or any future one) is forced into the exact same Azure region, with no override possible short of editing the module’s source. subscription_id is subtler: a variable "subscription_id" genuinely exists at the root, with a default that happens to match the literal in the provider block — but the provider block reads the literal, not var.subscription_id, so the variable is a decoy that silently stops controlling anything the moment someone changes it expecting the deploy target to move with it.

Fix: add variable "location" { type = string } to the module, thread it through as location = var.location; change the provider block to subscription_id = var.subscription_id.

modules/network-compute/main.tf
resource "azurerm_resource_group" "this" {
name = "${var.name}-rg"
location = var.location
}
resource "azurerm_virtual_network" "this" {
name = "${var.name}-vnet"
address_space = [var.vnet_cidr]
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
}
resource "azurerm_subnet" "this" {
for_each = toset(var.subnet_names)
name = each.value
resource_group_name = azurerm_resource_group.this.name
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = [cidrsubnet(var.vnet_cidr, var.subnet_newbits, index(var.subnet_names, each.value))]
}
resource "azurerm_network_security_group" "web" {
name = "${var.name}-web-nsg"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
security_rule {
name = "allow-https"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_network_interface" "app" {
name = "${var.name}-nic"
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.this[var.subnet_names[0]].id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "app" {
name = "${var.name}-vm"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
size = var.vm_size
admin_username = "azureuser"
disable_password_authentication = false
admin_password = var.admin_password
network_interface_ids = [azurerm_network_interface.app.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
lifecycle {
prevent_destroy = true
}
}
modules/network-compute/variables.tf
variable "name" {
type = string
}
variable "location" {
type = string
}
variable "vnet_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "subnet_newbits" {
type = number
default = 4
}
variable "subnet_names" {
type = list(string)
}
variable "vm_size" {
type = string
default = "Standard_B1s"
}
variable "admin_password" {
type = string
sensitive = true
}
modules/network-compute/outputs.tf
output "vnet_id" {
value = azurerm_virtual_network.this.id
}
output "vnet_address_space" {
value = azurerm_virtual_network.this.address_space
}
output "subnet_ids" {
value = { for k, s in azurerm_subnet.this : k => s.id }
}
output "nsg_id" {
value = azurerm_network_security_group.web.id
}
output "vm_id" {
value = azurerm_linux_virtual_machine.app.id
}
main.tf
terraform {
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 5.0" }
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
module "app" {
source = "./modules/network-compute"
for_each = toset(var.environments)
name = each.value
location = var.location
subnet_names = ["web", "app"]
admin_password = var.admin_password
}
variables.tf
variable "subscription_id" {
type = string
default = "00000000-0000-0000-0000-000000000000"
}
variable "location" {
type = string
default = "East US"
}
variable "environments" {
type = list(string)
default = ["dev", "staging"]
}
variable "admin_password" {
type = string
sensitive = true
default = "P@ssword-unused-in-this-proof-1234"
}
outputs.tf
output "vnet_ids" {
value = { for k, m in module.app : k => m.vnet_id }
}
output "vm_ids" {
value = { for k, m in module.app : k => m.vm_id }
}
tests/network_compute.tftest.hcl
mock_provider "azurerm" {}
variables {
name = "test"
location = "East US"
vnet_cidr = "10.5.0.0/16"
subnet_names = ["web", "app"]
admin_password = "unused-in-this-proof-P@ss1234"
}
override_resource {
target = azurerm_subnet.this["web"]
values = {
id = "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/web"
}
}
override_resource {
target = azurerm_subnet.this["app"]
values = {
id = "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/app"
}
}
override_resource {
target = azurerm_network_interface.app
values = {
id = "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/test-rg/providers/Microsoft.Network/networkInterfaces/test-nic"
}
}
run "creates_one_subnet_per_name" {
command = apply
module {
source = "./modules/network-compute"
}
assert {
condition = length(azurerm_subnet.this) == 2
error_message = "expected one subnet per subnet name"
}
assert {
condition = azurerm_network_interface.app.ip_configuration[0].subnet_id == azurerm_subnet.this["web"].id
error_message = "NIC should attach to the first subnet"
}
}
run "prevent_destroy_is_set" {
command = apply
module {
source = "./modules/network-compute"
}
assert {
condition = azurerm_subnet.this["web"].address_prefixes[0] != azurerm_subnet.this["app"].address_prefixes[0]
error_message = "subnets must not share an address prefix"
}
}

terraform validate on the corrected module+root tree: Success! The configuration is valid. — zero errors, zero warnings. terraform plan still cannot run offline against real azurerm (confirmed above), so terraform test with real mock_provider "azurerm" {} is this module’s actual offline proof:

$ terraform test
tests/network_compute.tftest.hcl... in progress
run "creates_one_subnet_per_name"... pass
run "prevent_destroy_is_set"... pass
tests/network_compute.tftest.hcl... tearing down
tests/network_compute.tftest.hcl... pass
Success! 2 passed, 0 failed.
#BugFix
1provider block inside the module, called with countProvider block removed from the module — real terraform init “Module is incompatible with count, for_each, and depends_on” before the fix
2cidrsubnet(count.index, var.subnet_newbits, var.vnet_cidr) — arguments in the wrong ordercidrsubnet(var.vnet_cidr, var.subnet_newbits, count.index) — real terraform test (mock_provider, command = plan) failure, invisible to validate
3Root output reads m.vnet_id, the module only exports vnet_address_spaceModule now exports a real vnet_id; root reads it — real terraform test (command = apply) “Unsupported attribute” before the fix
4No features {} block in the provider "azurerm" configurationfeatures {} added — real “Missing required argument” at validate (full tree) / plan (isolated) before the fix
5delete_os_disk_on_termination on azurerm_linux_virtual_machine (belongs to the classic azurerm_virtual_machine)Removed; os_disk block already controls disk behavior — real “Unsupported argument” before the fix
6required_providers uses >= 5.0~> 5.0 — real .terraform.lock.hcl shows the floating constraint silently resolved to 5.7.0
7count over subnet_names, index-shift on removalfor_each = toset(var.subnet_names) — real plan proved a removed middle name replaces/destroys the wrong indices
8No lifecycle block on azurerm_linux_virtual_machine.applifecycle { prevent_destroy = true } — real plan -destroy destroyed it with zero resistance before the fix
9location = "East US" hardcoded with no variable at all; subscription_id literal shadows a decoy variablelocation threaded through as a real variable; provider { subscription_id = var.subscription_id } — proven with real grep against the generated files

Five of these nine bugs (1, 2, 3, 4, 5) are hard failures — but proven across three different tools, because azurerm cannot plan offline in this course at all: bug 1 fails at terraform init, bugs 2 and 3 only surface through terraform test with mock_provider (the offline stand-in for plan), bug 4 fails at validate once real resources force the provider to configure, and bug 5 fails at plain validate. The other four never produce an error: an unpinned version, a count-indexed list, a missing lifecycle block, and a hardcoded region with a decoy variable next to it — the last one provable here only by reading the real generated files directly, since this cloud’s mock-credential ceiling is validate and test, not plan.

  • “If azurerm needs a real subscription to plan, none of this module’s bugs can be proven offline.” Proven above: five of nine bugs are hard failures caught by init/validate alone, and terraform test with mock_provider "azurerm" {} reproduces everything a real plan would have shown for the rest — including the exact same cidrsubnet and undefined-output errors the AWS review catches with plan directly.
  • “mock_provider accepts any resource however it’s shaped.” Proven above: the very first test run failed with a real Azure resource-ID parsing error from mocked, randomly-generated IDs — azurerm’s own SDK validates ID-shaped fields even under a mock, unlike AWS’s arbitrary-string IDs.
  • “An unused variable declaration is at least caught by validate.” Proven above (absence of proof, honestly reported): terraform validate on the buggy root returns clean, with no diagnostic at all about subscription_id being declared as a variable and then never referenced from the provider block that has the identical default value hardcoded instead.
flowchart TD
  A["terraform init"] -->|"1: provider in module + count"| A
  A --> B["terraform validate"]
  B -->|"4: features block missing"| B
  B -->|"5: delete_os_disk_on_termination hallucinated"| B
  B --> C["terraform test, mock_provider"]
  C -->|"2: cidrsubnet args swapped"| C
  C -->|"3: output reads m.vnet_id, undefined"| C
  C --> D["real diff against state"]
  D -->|"7: count index-shift"| D
  D -->|"8: no prevent_destroy"| D
  E[".terraform.lock.hcl"] -->|"6: unpinned provider resolved 5.7.0"| E
  F["grep against generated files"] -->|"9: location hardcoded, subscription_id decoy"| F
Where each bug sits in this module's evaluation path
Why does this error appear at terraform init rather than terraform validate?
Error: Module is incompatible with count, for_each, and depends_on
Why does the sibling AWS review prove its cidrsubnet argument-order bug with a real terraform plan, while this azurerm review proves the identical bug with terraform test and mock_provider instead?
The first version of the .tftest.hcl failed with parsing "rn1vk2cs": parsing the Subnet ID: the number of segments didn't match, despite mock_provider "azurerm" {} being declared. What does this prove?
A provider "azurerm" block with zero resources declared, and no features {} block, passes terraform validate. The identical missing features {} in a module+root tree WITH real resources fails validate with "Missing required argument". What explains the difference?
azurerm_subnet.this uses count = length(var.subnet_names). Three subnet names are applied for real, then the middle one is removed from the list. What does the real terraform plan show?
The module hardcodes location = "East US" with no variable at all, and the root hardcodes subscription_id as a literal even though a variable "subscription_id" with the same default exists. Since azurerm cannot plan offline here, how is this proven?