azure-schema-validation

star 7

Validate Azure Terraform resource schemas using MCP Terraform tools. Use this skill BEFORE implementing or modifying any Azure resource to ensure all attributes are correctly implemented according to the official provider documentation.

aztfmodnew By aztfmodnew schedule Updated 2/19/2026

name: azure-schema-validation description: Validate Azure Terraform resource schemas using MCP Terraform tools. Use this skill BEFORE implementing or modifying any Azure resource to ensure all attributes are correctly implemented according to the official provider documentation.

Azure Resource Schema Validation (Pattern 0)

CRITICAL: This is Pattern 0 - the MANDATORY first step before implementing or modifying any Terraform resource.

Why This is Mandatory

Validating the resource schema ensures:

  • ✅ All required attributes are implemented
  • ✅ All optional attributes are handled with proper try() patterns
  • ✅ Nested blocks are implemented as dynamic blocks
  • ✅ No attributes are missed or incorrectly typed
  • ✅ Resource is not deprecated
  • ✅ Correct default values are used

NEVER skip this step. AI training data may be outdated or incorrect. The Azure provider evolves rapidly.

Step-by-Step Validation Process

Step 1: Identify the Resource

Get the full Azure resource type name:

  • Format: azurerm_<resource_name>
  • Example: azurerm_managed_redis, azurerm_container_app, azurerm_kubernetes_cluster

Step 2: Search for Resource Documentation ID

Use MCP Terraform tool to find the numeric documentation ID for the resource:

Tool: mcp__terraform__search_providers
Parameters:
  provider_namespace: "hashicorp"
  provider_name: "azurerm"
  service_slug: "<resource_name>"       # e.g., "chaos_studio_target", "managed_redis"
  provider_document_type: "resources"

Example:

mcp__terraform__search_providers(
  provider_namespace="hashicorp",
  provider_name="azurerm",
  service_slug="managed_redis",
  provider_document_type="resources"
)

This returns a list with a numeric provider_doc_id (e.g., "8894603").

Step 3: Fetch Complete Documentation

Use the numeric provider_doc_id from Step 2 to fetch the full schema:

Tool: mcp__terraform__get_provider_details
Parameters:
  provider_doc_id: "<numeric_id_from_step_2>"    # e.g., "8894603"

Example:

mcp__terraform__get_provider_details(
  provider_doc_id="8894603"
)

This will return:

  • Full resource schema
  • All arguments (required and optional)
  • Nested block structures
  • Timeouts configuration
  • Import information
  • Deprecation status

Step 4: Extract Schema Information

From the documentation, extract:

A. Required Arguments

List all required arguments that MUST be present:

# Example for azurerm_managed_redis
resource_group_name = var.settings.resource_group_name  # Required, no try()
location            = var.settings.location             # Required, no try()
sku_name            = var.settings.sku_name            # Required, no try()

B. Optional Arguments

List all optional arguments with proper try() patterns:

# Arguments with no default (defaults to null)
family_name = try(var.settings.family_name, null)
capacity    = try(var.settings.capacity, null)

# Arguments with provider defaults (use same default)
minimum_tls_version = try(var.settings.minimum_tls_version, "1.2")
public_network_access_enabled = try(var.settings.public_network_access_enabled, true)

C. Nested Blocks

Identify blocks that should be implemented as dynamic blocks:

# Single optional block
dynamic "identity" {
  for_each = try(var.settings.identity, null) == null ? [] : [var.settings.identity]

  content {
    type         = identity.value.type
    identity_ids = try(identity.value.identity_ids, null)
  }
}

# Multiple blocks (list)
dynamic "access_policy" {
  for_each = try(var.settings.access_policies, [])

  content {
    tenant_id = access_policy.value.tenant_id
    object_id = access_policy.value.object_id
  }
}

# Multiple blocks (map - preferred for stable keys)
dynamic "zone_mapping" {
  for_each = try(var.settings.zone_mappings, {})

  content {
    zone_id   = zone_mapping.value.zone_id
    subnet_id = zone_mapping.value.subnet_id
  }
}

D. Timeouts Block

⚠️ CRITICAL: Not all resources support all timeout operations. Always verify which timeouts the resource supports from the MCP documentation before implementing.

  • Immutable resources (e.g., azurerm_chaos_studio_target) only support create, read, deletenever update
  • Mutable resources support all four: create, read, update, delete

Implement only the timeouts that the schema declares:

dynamic "timeouts" {
  for_each = try(var.settings.timeouts, null) == null ? [] : [var.settings.timeouts]

  content {
    create = try(timeouts.value.create, null)
    read   = try(timeouts.value.read, null)
    update = try(timeouts.value.update, null)  # ⚠️ Only if resource supports update
    delete = try(timeouts.value.delete, null)
  }
}

Step 5: Check Deprecation Status

CRITICAL: If the documentation shows the resource is deprecated:

  • ❌ DO NOT implement this resource
  • ❌ DO NOT extend this resource
  • ✅ Use the replacement resource mentioned in the deprecation notice
  • ✅ Inform the user about the deprecation and the correct alternative

Example:

DEPRECATED: azurerm_redis_cache is deprecated. Use azurerm_managed_redis instead.

Step 6: Document Validated Schema

Create a validation checklist for the resource:

## Schema Validation for azurerm\_<resource_name>

### Required Arguments

- [ ] argument1 - type: string
- [ ] argument2 - type: number

### Optional Arguments

- [ ] optional1 - type: string, default: null
- [ ] optional2 - type: bool, default: true
- [ ] optional3 - type: list(string), default: []

### Nested Blocks

- [ ] block1 (optional, single)
  - [ ] attribute1 - type: string
  - [ ] attribute2 - type: number
- [ ] block2 (optional, multiple)
  - [ ] attribute1 - type: string

### Timeouts

- [ ] create
- [ ] read
- [ ] update
- [ ] delete

### Deprecation Status

- [ ] NOT deprecated ✅
- [ ] OR deprecated → use <replacement_resource> instead

Implementation Guidelines

After validating the schema, implement the resource following these patterns:

Required Arguments Pattern

# No try() - must be provided
required_argument = var.settings.required_argument

Optional Arguments Pattern

# No default value in provider
optional_argument = try(var.settings.optional_argument, null)

# Has default value in provider (match the default)
optional_with_default = try(var.settings.optional_with_default, "provider_default_value")

Boolean Arguments Pattern

# Explicitly default to provider default
enabled = try(var.settings.enabled, true)  # If provider defaults to true
disabled = try(var.settings.disabled, false)  # If provider defaults to false

List Arguments Pattern

# Empty list as default
allowed_values = try(var.settings.allowed_values, [])

# Null as default if provider doesn't require list
optional_list = try(var.settings.optional_list, null)

Map Arguments Pattern

# Empty map as default
configurations = try(var.settings.configurations, {})

Standard Attributes Pattern

# Always use locals for these
location            = local.location
resource_group_name = local.resource_group_name
tags                = merge(local.tags, try(var.settings.tags, null))

Variables.tf Type Definition

After schema validation, define the variable with complete type specification:

variable "settings" {
  description = <<DESCRIPTION
    Settings object for azurerm_<resource_name>. Configuration attributes:

    Required:
      - required_arg1 - (string) Description from Azure docs
      - required_arg2 - (number) Description from Azure docs

    Optional:
      - optional_arg1 - (string) Description. Defaults to null.
      - optional_arg2 - (bool) Description. Defaults to true.
      - nested_block - (object) Optional nested configuration:
          - nested_attr1 - (string) Description
          - nested_attr2 - (number) Description

    DESCRIPTION

  type = object({
    # Required arguments
    required_arg1 = string
    required_arg2 = number

    # Optional arguments
    optional_arg1 = optional(string)
    optional_arg2 = optional(bool)

    # Optional nested block
    nested_block = optional(object({
      nested_attr1 = string
      nested_attr2 = number
    }))

    # Standard optional attributes
    location            = optional(string)
    resource_group_name = optional(string)
    tags                = optional(map(string))

    # Timeouts
    timeouts = optional(object({
      create = optional(string)
      read   = optional(string)
      update = optional(string)
      delete = optional(string)
    }))
  })

  validation {
    condition = length(setsubtract(keys(var.settings), [
      "required_arg1", "required_arg2",
      "optional_arg1", "optional_arg2",
      "nested_block",
      "location", "resource_group_name", "tags",
      "timeouts"
    ])) == 0
    error_message = "Unsupported attributes detected in settings. See variable description for allowed attributes."
  }
}

Common Validation Pitfalls

❌ Mistake 1: Skipping Validation

# ❌ WRONG - Guessing attributes
resource "azurerm_resource" "example" {
  name     = var.settings.name
  location = var.settings.location
  # Missing many required and optional attributes!
}

❌ Mistake 2: Incorrect Default Values

# ❌ WRONG - Using wrong default
enabled = try(var.settings.enabled, false)  # Provider default is true!

# ✅ CORRECT - Match provider default
enabled = try(var.settings.enabled, true)

❌ Mistake 3: Missing Nested Blocks

# ❌ WRONG - Not implementing nested blocks from schema
resource "azurerm_resource" "example" {
  name = var.settings.name
  # Missing identity block that exists in schema!
}

# ✅ CORRECT - Implement all nested blocks
resource "azurerm_resource" "example" {
  name = var.settings.name

  dynamic "identity" {
    for_each = try(var.settings.identity, null) == null ? [] : [var.settings.identity]
    content {
      type = identity.value.type
    }
  }
}

❌ Mistake 4: Not Checking Deprecation

# ❌ WRONG - Implementing deprecated resource
resource "azurerm_redis_cache" "example" {
  # This resource is DEPRECATED!
}

# ✅ CORRECT - Use replacement
resource "azurerm_managed_redis" "example" {
  # Use the current, non-deprecated resource
}

Quick Reference: MCP Terraform Tools

Step 1: Search for Resource (get numeric doc ID)

Tool: mcp__terraform__search_providers
Parameters:
  provider_namespace: "hashicorp"
  provider_name: "azurerm"
  service_slug: "<resource_name>"        # e.g., "chaos_studio_target"
  provider_document_type: "resources"

Step 2: Get Full Resource Schema

Tool: mcp__terraform__get_provider_details
Parameters:
  provider_doc_id: "<numeric_id_from_search>"   # e.g., "8894603"

List All Provider Resources

Tool: mcp__terraform__get_provider_capabilities
Parameters:
  namespace: "hashicorp"
  name: "azurerm"

Validation Checklist

Before implementing or modifying a resource:

  • Identified full resource type name (azurerm_*)
  • Fetched provider documentation using MCP Terraform tools
  • Verified resource is NOT deprecated
  • Extracted all required arguments
  • Extracted all optional arguments with defaults
  • Identified all nested blocks
  • Documented timeouts support
  • Created complete type definition in variables.tf
  • Implemented all arguments in resource block
  • Added dynamic blocks for nested configuration
  • Added timeouts block
  • Ready to proceed with implementation

When to Re-validate

Re-run schema validation:

  • When Azure provider version updates
  • When adding new attributes to existing resource
  • When users report missing attributes
  • When extending module with new features
  • Annually for actively maintained modules

Remember

Pattern 0 is MANDATORY. Never skip schema validation. Your module is only as good as its adherence to the official Azure provider schema.

If validation reveals the resource is deprecated, STOP and use the replacement resource instead.

Install via CLI
npx skills add https://github.com/aztfmodnew/terraform-azurerm-caf --skill azure-schema-validation
Repository Details
star Stars 7
call_split Forks 5
navigation Branch main
article Path SKILL.md
More from Creator