Skip to content

feat(terraform): add the Azure vnet module - #14099

Merged
justin-tahara merged 1 commit into
mainfrom
jtahara/azure-tf-vnet
Aug 20, 2026
Merged

feat(terraform): add the Azure vnet module#14099
justin-tahara merged 1 commit into
mainfrom
jtahara/azure-tf-vnet

Conversation

@justin-tahara

@justin-tahara justin-tahara commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Stacked PR 1 of 7. Each PR targets the branch below it, so GitHub retargets the next one to main as each merges. Review and merge bottom-up.

  1. feat(terraform): add the Azure vnet module #14099vnet, network, subnets, NAT gateway ← this PR
  2. feat(terraform): add the Azure storage module #14100storage, storage account + container
  3. feat(terraform): add the Azure postgres module #14101postgres, flexible server + alerts
  4. feat(terraform): add the Azure redis module #14102redis, cache + private endpoint
  5. feat(terraform): add the Azure aks module #14103aks, cluster + workload identity
  6. feat(terraform): add the Azure waf module #14104waf, regional WAF policy
  7. feat(terraform): add the Azure onyx composition and README #14105onyx, composition + README

Description

First module of the Azure set, mirroring deployment/terraform/modules/aws/vpc. Creates a virtual network, its subnets, a NAT gateway for stable egress, and optional flow logs.

Azure differs from AWS in three ways that show up in the interface:

  • Subnets are named resources and delegation is a property of the subnet, so the public/private CIDR lists become a map keyed by purpose. PostgreSQL Flexible Server needs its own delegated subnet, and Application Gateway needs a dedicated one it cannot share.
  • The Microsoft.Storage service endpoint on the AKS subnet plays the role the S3 gateway endpoint plays on AWS: it is what lets the storage account restrict access to the cluster.
  • Flow logs are opt-in rather than on by default. Azure writes them to a storage account and needs a Network Watcher in the region, so enabling them by default would either create a storage account the caller did not ask for or fail on subscriptions without a Network Watcher. The onyx composition at the top of this stack wires them up.

One bug worth calling out, caught while writing the tests: the AWS modules retain logs for 400 days, but Azure caps flow log retention at 365. Carrying the AWS default across would have failed at apply.

How Has This Been Tested?

terraform test against a mocked azurerm provider, so the suite needs no Azure subscription and no credentials:

cd deployment/terraform/modules/azure/vnet
terraform init -backend=false && terraform test
# Success! 8 passed, 0 failed.

The suite covers the default subnet layout, the postgres delegation, which subnets attach to the NAT gateway, and every input validation. I mutation-checked it: making all subnets attach to the NAT gateway fails nat_gateway_attaches_only_to_opted_in_subnets and nothing else.

terraform validate and the repo's ods fmt tf / ods lint tf / terraform_validate hooks all pass.

Not applied against a live subscription.

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

Changes from review (greptile, cubic)

  • Flow logs now require a Network Watcher, not just a storage account. The validation checked only the destination, so a plan would pass and the apply would fail with an unclear azurerm error. Both preconditions now sit together on enable_flow_logs.
  • Documented which subnets can carry a NAT gateway, in the subnets variable.

Tests: 8 → 10.

One suggestion not taken, and why

cubic asked for a validation rejecting a NAT gateway on a delegated or Application Gateway subnet. I documented it instead. Azure does support a NAT gateway on a subnet delegated to Flexible Server; it is Application Gateway subnets that cannot have one, and this module neither creates the gateway nor knows which subnet will hold it — the map keys are arbitrary. A blanket validation would reject legal configurations to catch a case the module cannot actually identify. The defaults already set nat_gateway = false on both.

Round 2

  • A blank network_watcher_name no longer passes. The null check accepted " ", which then failed in the provider.
  • nat_gateway is now opt-in (default false). This is the better answer to the round-1 comment I pushed back on: rather than validating against a case the module cannot detect, a caller writing their own subnet map now has to ask for egress instead of remembering to refuse it. The default map opts the AKS subnet in explicitly.
  • Provider floor raised to >= 4.11.0, < 5.0.

Tests: 10 → 12.

One P1 not taken: it is factually wrong

cubic says this module "cannot validate or apply with azurerm 4.x because the flow-log resource uses an unsupported target argument."

It validates. I measured it: target_resource_id is absent in 4.10.0 and present from 4.11.0, and our lock resolves 4.81.0, where terraform validate and all 12 tests pass. cubic's suggested alternative — wiring an NSG ID and using network_security_group_id — would move us onto the deprecated argument and give NSG flow logs instead of VNet flow logs.

Its companion P2 was right, though, and is fixed above: ~> 4.0 allowed 4.0.x, where the field genuinely does not exist. That is the real bug, and the version bisect is what found the exact floor.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces the first Azure Terraform module, provisioning a virtual network with configurable subnets, stable NAT-based egress, and optional Network Watcher flow logs.

  • Creates the VNet, purpose-keyed subnets, PostgreSQL delegation, and storage service endpoint configuration.
  • Adds an opt-in per-subnet NAT association model with a stable public IP.
  • Adds optional VNet flow logs with storage, Network Watcher, and retention validation.
  • Exposes network resource IDs and includes mocked-provider Terraform tests for defaults and validation behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the revised validation rejects both omitted and blank Network Watcher names when flow logs are enabled.

Important Files Changed

Filename Overview
deployment/terraform/modules/azure/vnet/main.tf Defines the VNet, subnets, NAT gateway associations, public IP, and optional VNet flow-log resource.
deployment/terraform/modules/azure/vnet/variables.tf Defines typed network inputs and validates subnet, NAT, retention, storage-account, and nonblank Network Watcher requirements.
deployment/terraform/modules/azure/vnet/tests/vnet.tftest.hcl Exercises default topology, NAT opt-in behavior, flow-log prerequisites, blank watcher rejection, and Azure input limits.
deployment/terraform/modules/azure/vnet/versions.tf Sets Terraform 1.12 and AzureRM 4.11–4.x compatibility bounds required for VNet flow logs.
deployment/terraform/modules/azure/vnet/outputs.tf Exposes VNet, subnet, NAT gateway, and stable egress address information for downstream modules.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Inputs[Module inputs] --> VNet[Azure Virtual Network]
  VNet --> Subnets[Purpose-keyed subnets]
  Subnets --> AKS[AKS subnet]
  Subnets --> PostgreSQL[Delegated PostgreSQL subnet]
  Subnets --> PrivateEndpoints[Private endpoint subnet]
  Subnets --> AppGateway[Application Gateway subnet]
  AKS --> NAT[NAT gateway]
  NAT --> PublicIP[Stable public IP]
  VNet -. optional .-> FlowLogs[Network Watcher flow logs]
  FlowLogs --> Storage[Flow-log storage account]
Loading

Reviews (3): Last reviewed commit: "feat(terraform): add the Azure vnet modu..." | Re-trigger Greptile

Comment thread deployment/terraform/modules/azure/vnet/variables.tf

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files

Confidence score: 3/5

  • In deployment/terraform/modules/azure/vnet/main.tf, custom subnet maps can create prohibited NAT associations for delegated or app_gateway subnets, risking an invalid deployment; reject these combinations through input validation.
  • In deployment/terraform/modules/azure/vnet/variables.tf, enabling flow logs validates only flow_log_storage_account_id while network_watcher_name is also required by the flow-log resource, allowing an invalid plan; require both values when flow logs are enabled.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="deployment/terraform/modules/azure/vnet/variables.tf">

<violation number="1" location="deployment/terraform/modules/azure/vnet/variables.tf:119">
P2: The enable_flow_logs validation requires flow_log_storage_account_id but not network_watcher_name, even though the flow log resource in main.tf needs both. With default null network_watcher_name, plan succeeds and the module fails only at apply with an unclear azurerm error instead of failing loudly in validation. Since the flow log also needs a Network Watcher, add `var.network_watcher_name != null` to the validation condition so misconfiguration is caught at plan time.</violation>
</file>

<file name="deployment/terraform/modules/azure/vnet/main.tf">

<violation number="1" location="deployment/terraform/modules/azure/vnet/main.tf:5">
P2: When a custom subnet map opts a delegated subnet or `app_gateway` into NAT, this comprehension creates the prohibited subnet association. Reject those combinations with input validation instead of relying on callers to preserve the default `false` values.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread deployment/terraform/modules/azure/vnet/variables.tf
Comment thread deployment/terraform/modules/azure/vnet/main.tf
@justin-tahara
justin-tahara force-pushed the jtahara/azure-tf-vnet branch from 7665c83 to c98a9e8 Compare August 19, 2026 22:47
Comment thread deployment/terraform/modules/azure/vnet/variables.tf Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 5 files

Confidence score: 2/5

  • deployment/terraform/modules/azure/vnet/main.tf and variables.tf can fail validation or planning with azurerm 4.x when flow logs are enabled because the flow-log resource uses an unsupported target argument; update the resource to use an NSG ID and network_security_group_id, or constrain the supported provider version.
  • deployment/terraform/modules/azure/vnet/variables.tf defaults omitted nat_gateway values to true for custom subnets, which can attach NAT to every subnet and break configurations such as Application Gateway subnets; make NAT opt-in or require callers to set it explicitly.
  • deployment/terraform/modules/azure/vnet/variables.tf allows empty or whitespace-only network_watcher_name values through flow-log validation, deferring failure to provider planning or Azure apply; reject blank values during input validation.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="deployment/terraform/modules/azure/vnet/main.tf">

<violation number="1" location="deployment/terraform/modules/azure/vnet/main.tf:90">
P2: When a caller's lock file selects azurerm 4.0.x, Terraform rejects this resource before planning because that provider schema has no `target_resource_id`. Raise the module's minimum azurerm version to the release that introduced this field.</violation>
</file>

<file name="deployment/terraform/modules/azure/vnet/variables.tf">

<violation number="1" location="deployment/terraform/modules/azure/vnet/variables.tf:34">
P1: When a caller supplies a custom `subnets` map, omitting `nat_gateway` defaults it to true and attaches NAT to every subnet. This makes the setting opt-out instead of opt-in and can cause an Application Gateway subnet deployment to fail; default it to false and explicitly opt in the AKS default.</violation>

<violation number="2" location="deployment/terraform/modules/azure/vnet/variables.tf:118">
P1: When `enable_flow_logs` is true, this module cannot validate or apply with azurerm 4.x because the flow-log resource uses an unsupported target argument. Wire an NSG ID and use `network_security_group_id`, or use a provider resource that supports VNet flow logs.</violation>

<violation number="3" location="deployment/terraform/modules/azure/vnet/variables.tf:129">
P2: Reject empty and whitespace-only `network_watcher_name` values when flow logs are enabled. Otherwise invalid input passes this validation and fails later during provider planning or Azure apply.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread deployment/terraform/modules/azure/vnet/variables.tf Outdated
Comment thread deployment/terraform/modules/azure/vnet/variables.tf
Comment thread deployment/terraform/modules/azure/vnet/main.tf
Comment thread deployment/terraform/modules/azure/vnet/variables.tf Outdated
First module of the Azure set, mirroring deployment/terraform/modules/aws/vpc.

Azure differs from AWS in three ways that show up in the interface:

- Subnets are named resources and delegation is a property of the subnet, so
  the public/private CIDR lists become a map keyed by purpose. PostgreSQL
  Flexible Server needs its own delegated subnet, and Application Gateway needs
  a dedicated one.
- The Microsoft.Storage service endpoint on the AKS subnet plays the role the
  S3 gateway endpoint plays on AWS: it is what lets the storage account
  restrict access to the cluster.
- Flow logs are opt-in rather than on by default. Azure writes them to a
  storage account and needs a Network Watcher in the region, so enabling them
  by default would either create a storage account the caller did not ask for
  or fail on subscriptions without a Network Watcher.

Tests plan the module against a mocked provider, so they need no Azure
subscription. Run 'terraform test' from the module directory.
@justin-tahara
justin-tahara force-pushed the jtahara/azure-tf-vnet branch from c98a9e8 to 0c74f24 Compare August 19, 2026 23:14
@justin-tahara
justin-tahara added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main with commit d7835c0 Aug 20, 2026
49 checks passed
@justin-tahara
justin-tahara deleted the jtahara/azure-tf-vnet branch August 20, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants