Skip to content

Module Resource Validation

Terratags can validate resources created by external modules (from Terraform Registry, Git repositories, etc.) by analyzing Terraform plan output. This provides comprehensive tag compliance checking across your entire infrastructure.

Overview

When using external modules in Terraform, the actual resources created by those modules are not visible during static analysis of your Terraform files. Terratags solves this by analyzing the Terraform plan output, which contains the expanded resource tree including all module-created resources.

How It Works

Static Analysis Limitation

When analyzing Terraform files directly (-dir flag), terratags can only see: - Direct resource blocks in your .tf files - Module calls and their input variables/tags - Provider configurations

It cannot see: - Resources created inside external modules - Conditional resources based on module logic - Dynamic resource creation patterns

Plan-Based Analysis

When analyzing Terraform plan JSON (-plan flag), terratags can see: - All direct resources from your .tf files - All resources that will be created by external modules - The complete resource dependency tree - Actual tag values after variable resolution

Tag Inheritance

Tags passed to module calls are automatically inherited by resources created within those modules:

  1. Module Call Tags: Tags defined in the module block
  2. Resource Tags: Tags defined directly on resources within modules
  3. Provider Default Tags: Tags from provider default_tags configuration
  4. Inheritance Priority: Resource tags > Module tags > Provider default tags

Validation Modes

Mode Command Scope Use Case
Directory terratags -config config.yaml -dir ./infra Direct resources + module calls Quick validation, CI/CD for file changes
Plan terratags -config config.yaml -plan plan.json All resources (direct + module-created) Comprehensive validation, pre-deployment

Examples

Module Validation Scenario

Consider this Terraform configuration:

# main.tf
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "3.14.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-west-2a", "us-west-2b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  tags = {
    Environment = "production"
    Owner       = "platform-team"
    Project     = "infrastructure"
    # All required tags present for module call
  }
}

resource "aws_s3_bucket" "app_data" {
  bucket = "my-app-data"

  tags = {
    Environment = "production"
    Owner       = "app-team"
    # Missing Project tag - will be flagged
  }
}

Directory Validation

terratags -config config.yaml -dir .

Results: - ✅ Module vpc call has all required tags - ❌ Resource aws_s3_bucket.app_data missing Project tag - ❓ Unknown: What about the VPC, subnets, route tables, etc. created by the module?

Plan Validation

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
terratags -config config.yaml -plan plan.json

Results: - ✅ Module vpc call has all required tags - ❌ Resource aws_s3_bucket.app_data missing Project tag - ✅ module.vpc.aws_vpc.this[0] inherits tags from module call - ✅ module.vpc.aws_subnet.private[0] inherits tags from module call - ✅ module.vpc.aws_subnet.private[1] inherits tags from module call - ✅ module.vpc.aws_subnet.public[0] inherits tags from module call - ✅ module.vpc.aws_subnet.public[1] inherits tags from module call - ✅ module.vpc.aws_route_table.private[0] inherits tags from module call - ... and so on for all module-created resources

Configuration

Module validation works with all existing terratags configuration options:

Required Tags

required_tags:
  - Name
  - Environment
  - Owner
  - Project

Pattern Validation

required_tags:
  Environment:
    pattern: "^(dev|test|staging|prod)$"
  Owner:
    pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
  Project: {}
  Name: {}

Exemptions

exemptions:
  - resource_type: aws_vpc
    resource_name: "*"
    exempt_tags: [Name]
    reason: "VPC names are managed by module"

  - resource_type: aws_subnet
    resource_name: "*"
    exempt_tags: [Name]
    reason: "Subnet names are auto-generated by module"

Reporting

Console Output

Plan validation provides detailed output distinguishing between direct and module resources:

INFO    Found 1 direct resources and 12 module resources
INFO    Direct resources: 0/1 compliant (0.0%)
INFO    Module resources: 12/12 compliant
INFO    Overall: 12/13 compliant (92.3%)

Resource aws_s3_bucket 'app_data' is missing required tags: Project

HTML Reports

HTML reports include separate sections for: - Direct Resources: Resources defined in your .tf files - Module Resources: Resources created by external modules

Module resources show additional information: - Module path (e.g., module.vpc) - Module source (e.g., terraform-aws-modules/vpc/aws@3.14.0) - Tag inheritance details

Best Practices

1. Use Plan Validation for Production

For production deployments, always use plan validation to ensure comprehensive coverage:

# Production deployment pipeline
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
terratags -config config.yaml -plan plan.json

2. Use Directory Validation for Development

For quick feedback during development, directory validation is sufficient:

# Development workflow
terratags -config config.yaml -dir .

3. Configure Module-Specific Exemptions

Many modules auto-generate resource names or have specific tagging patterns:

exemptions:
  # VPC module resources often have auto-generated names
  - resource_type: aws_vpc
    resource_name: "*"
    exempt_tags: [Name]
    reason: "VPC names managed by terraform-aws-modules/vpc"

  # Security group rules don't typically need all tags
  - resource_type: aws_security_group_rule
    resource_name: "*"
    exempt_tags: [Name, Project]
    reason: "Security group rules inherit context from parent"

4. Standardize Module Tags

Ensure consistent tagging across modules by standardizing tag inputs:

# Standard module tags variable
variable "common_tags" {
  description = "Common tags to apply to all resources"
  type        = map(string)
  default = {
    Environment = "production"
    Owner       = "platform-team"
    Project     = "infrastructure"
    ManagedBy   = "terraform"
  }
}

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  # Module-specific configuration
  name = "my-vpc"
  cidr = "10.0.0.0/16"

  # Apply standard tags
  tags = var.common_tags
}

module "rds" {
  source = "terraform-aws-modules/rds/aws"

  # Module-specific configuration
  identifier = "my-database"

  # Apply standard tags
  tags = var.common_tags
}

Limitations

1. Plan Generation Required

Module validation requires generating a Terraform plan, which means: - AWS/Azure credentials must be available - Terraform must be able to connect to providers - Plan generation time adds to validation time

2. Module Source Access

For private modules, ensure the validation environment has access: - Private Git repositories require SSH keys or tokens - Private Terraform registries require authentication - Network access to module sources

3. Dynamic Module Behavior

Some advanced module patterns may not be fully captured: - Modules that create resources based on external data sources - Modules with complex conditional logic - Modules that use provider aliases extensively

Troubleshooting

Common Issues

Issue: Module resources not appearing in validation

Found 1 direct resources and 0 module resources

Solution: Ensure the plan includes module resources:

# Check plan contains module resources
terraform show -json tfplan | jq '.resource_changes[] | select(.module_address != null)'

Issue: Tag inheritance not working

Module resource missing tags that were passed to module call

Solution: Verify module call tags are being loaded:

# Run with verbose logging
terratags -config config.yaml -plan plan.json -verbose

Issue: Module source showing as "unknown"

Module: module.vpc (unknown)

Solution: Ensure plan includes configuration section:

# Generate plan with configuration
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# Check configuration section exists
jq '.configuration.root_module.module_calls' plan.json

Integration Examples

GitHub Actions

name: Validate All Resources

on:
  pull_request:
    paths: ['**.tf']

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.21'

      - name: Install Terratags
        run: go install github.com/terratags/terratags@latest

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}

      - name: Terraform Init
        run: terraform init

      - name: Generate Plan
        run: |
          terraform plan -out=tfplan
          terraform show -json tfplan > plan.json

      - name: Validate Tags
        run: terratags -config .terratags.yaml -plan plan.json -report report.html

      - name: Upload Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: tag-validation-report
          path: report.html

GitLab CI

validate-tags:
  stage: validate
  image: hashicorp/terraform:latest
  before_script:
    - apk add --no-cache go
    - go install github.com/terratags/terratags@latest
  script:
    - terraform init
    - terraform plan -out=tfplan
    - terraform show -json tfplan > plan.json
    - terratags -config .terratags.yaml -plan plan.json
  artifacts:
    when: always
    reports:
      junit: report.xml
    paths:
      - plan.json
      - report.html

This comprehensive module validation capability ensures that your tag compliance policies are enforced across your entire Terraform-managed infrastructure, not just the resources you define directly.