Struktur #

Module yang ditulis dengan baik bukan hanya soal kode yang benar — tapi soal kode yang mudah dipahami dan dipakai orang lain. Struktur file yang konsisten, README yang informatif, dan penamaan yang jelas adalah perbedaan antara module yang langsung dipercaya dan dipakai, dengan module yang membuat orang ragu dan akhirnya menulis ulang sendiri. Artikel ini membahas konvensi struktural yang diterima luas di komunitas Terraform.

flowchart TD
    subgraph MOD["Module Structure"]
        A["main.tf\nResource utama"]
        B["variables.tf\nInput variables"]
        C["outputs.tf\nOutput values"]
        D["versions.tf\nProvider & TF version"]
    end
    A --> E["🏗️ Infrastructure\nResources"]
    B --> A
    A --> C

    style MOD fill:#f8f9fa,stroke:#495057
    style A fill:#3b82f6,stroke:#1e40af,color:#fff
    style B fill:#10b981,stroke:#059669,color:#fff
    style C fill:#f59e0b,stroke:#d97706,color:#fff
    style D fill:#8b5cf6,stroke:#6d28d9,color:#fff

Struktur File Standar #

Komunitas Terraform dan HashiCorp sendiri merekomendasikan struktur minimal yang konsisten untuk setiap module.

modules/vpc/
  ├── main.tf          ← Resource utama — isi utama module
  ├── variables.tf     ← Semua input variable dengan deskripsi lengkap
  ├── outputs.tf       ← Semua output dengan deskripsi penggunaan
  ├── versions.tf      ← Terraform dan provider version constraints
  └── README.md        ← Dokumentasi: penggunaan, input, output, contoh

File tambahan yang mungkin dibutuhkan untuk module yang lebih kompleks:

modules/vpc/
  ├── main.tf
  ├── variables.tf
  ├── outputs.tf
  ├── versions.tf
  ├── README.md
  ├── locals.tf        ← Local values yang dipakai di seluruh module
  ├── data.tf          ← Data source yang dibutuhkan module
  └── examples/        ← Contoh penggunaan yang bisa langsung dijalankan
      ├── basic/
      │   ├── main.tf
      │   └── README.md
      └── complete/
          ├── main.tf
          └── README.md

versions.tf — Version Constraints Module #

Setiap module sebaiknya mendefinisikan versi Terraform dan provider minimum yang dibutuhkan. Ini mencegah module dijalankan di versi yang tidak kompatibel.

# modules/vpc/versions.tf
terraform {
  required_version = ">= 1.3.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0"
      # Gunakan >= bukan ~> di module — biarkan root module yang pin versi spesifik
      # Module yang terlalu ketat soal versi menyulitkan pengguna
    }
  }
}
Di module, gunakan constraint >= (minimal version) bukan ~> (pessimistic constraint). Root module yang bertanggung jawab memilih versi spesifik — module hanya perlu menyatakan versi minimum yang dibutuhkan agar fitur yang digunakan tersedia.

README.md yang Berguna #

README adalah hal pertama yang dilihat seseorang sebelum memutuskan apakah mau menggunakan module. README yang baik menjawab tiga pertanyaan: apa yang dilakukan module ini, bagaimana cara menggunakannya, dan apa yang akan didapat.

# Module: VPC

Module ini membuat VPC standar dengan public dan private subnet,
Internet Gateway, NAT Gateway, dan routing table yang dikonfigurasi
dengan benar untuk setiap tier.

## Penggunaan

```hcl
module "vpc" {
  source  = "../../modules/vpc"

  cidr_block           = "10.0.0.0/16"
  environment          = "production"
  public_subnet_count  = 3
  private_subnet_count = 3
}

Input #

NamaDeskripsiTipeDefaultWajib
cidr_blockCIDR block untuk VPCstringYa
environmentNama environmentstringYa
public_subnet_countJumlah public subnetnumber2Tidak
private_subnet_countJumlah private subnetnumber2Tidak

Output #

NamaDeskripsi
vpc_idID VPC yang dibuat
public_subnet_idsList ID public subnet
private_subnet_idsList ID private subnet

Requirements #

NamaVersi
terraform>= 1.3.0
aws>= 5.0

---

## Pengorganisasian Module dalam Repository

Ada dua pendekatan umum untuk mengorganisasi module: monorepo dan multirepo. Untuk tim yang baru memulai, monorepo adalah titik awal yang lebih sederhana.

MONOREPO — semua module dalam satu repository:

infrastructure/ ├── modules/ │ ├── vpc/ │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ └── README.md │ ├── eks/ │ │ └── … │ ├── rds/ │ │ └── … │ └── iam-role/ │ └── … └── environments/ ├── dev/ │ └── main.tf ← source = “../../modules/vpc” ├── staging/ │ └── main.tf └── production/ └── main.tf

MULTIREPO — setiap module dalam repository terpisah:

github.com/org/terraform-module-vpc/ ├── main.tf ├── variables.tf ├── outputs.tf └── README.md

github.com/org/terraform-module-eks/ └── …

Pemanggil menggunakan Git URL sebagai source: #

module “vpc” { source = “git::https://github.com/org/terraform-module-vpc.git?ref=v2.1.0”

#

}


```mermaid
flowchart TD
    A["Root Module"] -->|"source, variables"| B["modules/vpc"]
    A -->|"source, variables"| C["modules/compute"]
    B -->|"vpc_id, subnet_ids"| C
    B -->|"outputs"| D["📡 Outputs ke\ninfra lain"]
    C -->|"instance_ids"| D

    style A fill:#3b82f6,stroke:#1e40af,color:#fff
    style B fill:#10b981,stroke:#059669,color:#fff
    style C fill:#f59e0b,stroke:#d97706,color:#fff
    style D fill:#8b5cf6,stroke:#6d28d9,color:#fff

Konvensi Penamaan Internal Module #

Di dalam module, ada konvensi penamaan yang membantu konsistensi dan keterbacaan.

# KONVENSI: Gunakan "this" untuk resource utama di module single-purpose
# (module yang dibuat untuk satu jenis resource utama)

resource "aws_vpc" "this" {      # Bukan "main" atau nama spesifik
  cidr_block = var.cidr_block
}

resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id
}

# Output menggunakan nama yang deskriptif dari perspektif pemanggil
output "vpc_id" {
  value = aws_vpc.this.id        # Bukan "this_id" — lebih jelas untuk pemanggil
}

# KONVENSI: Prefix resource dengan nama module jika ada ambiguitas
# (module yang membuat beberapa jenis resource)
resource "aws_security_group" "web" {    # "web" lebih deskriptif dari "this"
  name = "${var.name}-web-sg"
}

resource "aws_security_group" "db" {
  name = "${var.name}-db-sg"
}

Struktur Module yang Kompleks #

Untuk module yang lebih besar, pertimbangkan memisahkan logik ke beberapa file berdasarkan komponen.

modules/eks-cluster/
  ├── main.tf              ← EKS cluster resource utama
  ├── node-groups.tf       ← Node group resources
  ├── iam.tf               ← IAM roles dan policies
  ├── security-groups.tf   ← Security group resources
  ├── addons.tf            ← EKS add-ons (CoreDNS, kube-proxy, dll.)
  ├── variables.tf         ← Semua input variable
  ├── outputs.tf           ← Semua output
  ├── locals.tf            ← Local values dan kalkulasi
  ├── data.tf              ← Data sources (AZ, AMI, dll.)
  ├── versions.tf          ← Version constraints
  └── README.md


Module Testing Strategy #

# Testing module sebelum publish
# 1. Unit test dengan terraform validate
terraform init
terraform validate
terraform fmt -check

# 2. Integration test dengan plan
terraform plan -out=test.tfplan

# 3. E2E test dengan apply + destroy
terraform apply -auto-approve
# Jalankan test assertions
terraform destroy -auto-approve

# Tools untuk automated testing:
# - Terratest (Go)
# - terraform test (built-in since 1.6)
# Terraform built-in test (terraform test)
# tests/vpc_test.tftest.hcl

run "verify_vpc_cidr" {
  command = plan

  assert {
    condition     = output.vpc_cidr == "10.0.0.0/16"
    error_message = "VPC CIDR should be 10.0.0.0/16"
  }
}

run "verify_subnet_count" {
  command = plan

  assert {
    condition     = length(output.private_subnet_ids) == 2
    error_message = "Should have 2 private subnets"
  }
}

Module Documentation Best Practices #

# Setiap module HARUS punya:
# 1. README.md — penjelasan, usage, contoh
# 2. variables.tf — semua input variable dengan description
# 3. outputs.tf — semua output dengan description
# 4. main.tf — resource definitions
# 5. versions.tf — provider version constraints

# Auto-generate docs dengan terraform-docs
brew install terraform-docs
terraform-docs markdown table . > README.md

Module Versioning #

# Git tag untuk versioning module
git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0

# Gunakan module dengan version pin
module "networking" {
  source = "git::ssh://[email protected]/my-org/modules.git//networking?ref=v1.0.0"
}

# Update ke versi baru
module "networking" {
  source = "git::ssh://[email protected]/my-org/modules.git//networking?ref=v1.1.0"
}
# Semantic versioning:
# MAJOR: Breaking change (hapus variable, ubah output)
# MINOR: New feature (tambah optional variable/output)
# PATCH: Bug fix (fix typo, update default)

# Version constraint:
module "networking" {
  source  = "git::ssh://[email protected]/my-org/modules.git//networking?ref=v1.x"
  # ~> 1.0 = v1.0.0 sampai v1.99.99
}

Testing Modules #

# Terratest: automated testing untuk Terraform modules
# (Go-based testing framework)

# Install
go install github.com/gruntwork-io/terratest/modules/terraform@latest

# Basic test structure:
# test/
# ├── networking_test.go
# └── fixtures/
#     └── main.tf

# Run tests
cd test && go test -v -timeout 30m
# Lightweight alternative: terraform validate + plan
# scripts/test-module.sh
#!/bin/bash
cd "$1"
terraform init -backend=false
terraform validate
terraform fmt -check
echo "Module $1 passed validation"

Module File Organization #

RECOMMENDED FILE STRUCTURE:

modules/
└── networking/
    ├── main.tf          # Main resources
    ├── variables.tf     # All input variables
    ├── outputs.tf       # All outputs
    ├── versions.tf      # Provider requirements
    ├── locals.tf        # Local values
    ├── data.tf          # Data sources
    └── README.md        # Auto-generated docs

# Alternatives:
# - Split by resource type (vpc.tf, subnet.tf, etc.)
# - Split by concern (security.tf, routing.tf)
# - Single file for small modules
# Auto-generate documentation
terraform-docs markdown table ./modules/networking > ./modules/networking/README.md

# Validate all modules
find modules -name "main.tf" -exec dirname {} \; | while read dir; do
  echo "Validating $dir"
  cd "$dir" && terraform init -backend=false && terraform validate
done

Ringkasan #

  • Struktur minimal module: main.tf, variables.tf, outputs.tf, versions.tf, dan README.md — konsistensi ini membuat siapapun langsung orientasi.
  • versions.tf di module menggunakan >= bukan ~> — biarkan root module yang pin versi spesifik, module hanya nyatakan minimum.
  • README adalah dokumentasi utama — jawab tiga pertanyaan: apa yang dilakukan, bagaimana cara pakai, dan apa yang didapat (tabel input/output).
  • Gunakan this sebagai nama resource utama di module single-purpose — mengurangi ambiguitas saat pemanggil membaca output module.
  • Monorepo untuk memulai — lebih mudah dikelola, refactoring lebih mudah, discovery lebih mudah sebelum ada kebutuhan multirepo.
  • Pisah file berdasarkan komponen untuk module yang kompleks — iam.tf, security-groups.tf, node-groups.tf lebih mudah dinavigasi dari satu main.tf raksasa.

← Sebelumnya: Apa itu Module?   Berikutnya: Root vs Child Module →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact