Versioning #

Module tanpa versioning adalah module yang tidak bisa dipercaya untuk production. Bayangkan dua tim menggunakan module VPC yang sama — tim A di-apply hari ini, tim B apply minggu depan. Jika di antara itu ada perubahan di module yang breaking, tim B akan mendapat hasil yang berbeda dari tim A tanpa ada peringatan apapun. Versioning adalah mekanisme yang memastikan setiap pemanggil mendapat versi module yang sama dengan yang mereka pilih, sampai mereka memutuskan untuk upgrade.

flowchart TD
    A["Module\nVersioning"] --> B["Semantic\nVersioning"]
    A --> C["Version\nConstraints"]
    A --> D["Lock\nFile"]

    B --> E["MAJOR.MINOR.PATCH\nBreaking.Features.Fixes"]
    C --> F["~>, >=, <\nConstraints"]
    D --> G[".terraform.lock.hcl\nDependency Lock"]

    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

Mengapa Module Perlu Versi #

TANPA VERSIONING:

Team A: terraform apply hari Senin
  → Menggunakan module VPC versi "terbaru"
  → Berhasil, infrastruktur berjalan

Developer module: commit perubahan ke module (Selasa)
  → Mengubah output dari "subnet_id" ke "subnet_ids" (breaking change)

Team B: terraform apply hari Rabu
  → Menggunakan module VPC versi "terbaru" (yang berbeda!)
  → ERROR: "module.vpc.subnet_id" tidak ada
  → Team B bingung — konfigurasi mereka tidak berubah

DENGAN VERSIONING:

module "vpc" {
  source  = "git::https://github.com/org/tf-module-vpc.git?ref=v1.2.0"
}

Team A dan Team B keduanya menggunakan v1.2.0 → hasil yang sama.
Developer module rilis v2.0.0 dengan breaking change.
Team B tidak akan terdampak sampai mereka memutuskan upgrade ke v2.0.0.

Semantic Versioning untuk Module #

Module Terraform mengikuti konvensi semantic versioning (semver): MAJOR.MINOR.PATCH.

SEMANTIC VERSIONING:

v1.2.3
│ │ │
│ │ └── PATCH: Bug fix, tidak ada perubahan interface
│ │         v1.2.3 → v1.2.4: Fix bug di security group rule
│ │
│ └──── MINOR: Fitur baru, backward compatible
│           v1.2.0 → v1.3.0: Tambah support untuk IPv6 (opsional)
│           Pengguna v1.2.0 tidak terpengaruh
│
└────── MAJOR: Breaking change
            v1.x.x → v2.0.0: Rename output "subnet_id" → "subnet_ids"
            Semua pengguna v1.x.x harus update konfigurasi sebelum upgrade

CONTOH CHANGELOG YANG BAIK:

v2.0.0 (Breaking Change)
  BREAKING: Output "subnet_id" diganti menjadi "subnet_ids" (list)
  BREAKING: Variable "az_count" dihapus, gunakan "subnet_count"
  NEW: Support untuk NAT Gateway per-AZ

v1.3.0
  NEW: Tambah variable "enable_ipv6" (default: false)
  NEW: Output "ipv6_cidr_block" ditambahkan
  FIX: Security group tidak menghapus rule lama saat update

v1.2.4
  FIX: Subnet CIDR kalkulasi error saat VPC CIDR lebih kecil dari /16

Membuat Rilis Module di Git #

Untuk module yang disimpan di Git repository, versi ditentukan oleh Git tag.

# Workflow rilis module:

# 1. Pastikan semua perubahan sudah di-commit dan di-test
git add -A
git commit -m "feat: tambah support NAT Gateway per-AZ"

# 2. Buat tag dengan format v<MAJOR>.<MINOR>.<PATCH>
git tag v1.3.0

# 3. Push tag ke remote
git push origin v1.3.0

# 4. Opsional: buat GitHub Release dengan changelog
# (bisa lewat UI atau GitHub CLI)
gh release create v1.3.0 \
  --title "v1.3.0 — NAT Gateway per-AZ" \
  --notes "NEW: Support NAT Gateway per-AZ via variable enable_nat_per_az"
# Pemanggil menggunakan tag sebagai referensi versi
module "vpc" {
  source = "git::https://github.com/org/terraform-module-vpc.git?ref=v1.3.0"

  cidr_block  = "10.0.0.0/16"
  environment = var.environment
}

Versioning di Terraform Registry #

Untuk module yang dipublikasikan ke Terraform Registry (public atau private), versioning lebih formal dan menggunakan argumen version.

# Terraform Registry — versi dikontrol dengan version constraint
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.1"
  # ~> 5.1 = >=5.1.0, <6.0.0

  name = "production-vpc"
  cidr = "10.0.0.0/16"
}

# Constraint yang lebih ketat untuk production
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "= 20.2.1"
  # Pin ke versi exact — tidak ada surprise update
}
# ANTI-PATTERN: Tanpa version constraint di Registry module
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  # Tidak ada version — setiap terraform init bisa mendapat versi berbeda!
}
flowchart TD
    A["module \"vpc\"\nversion = \"~> 3.0\""] --> B["v3.0.0 ✅"]
    A --> C["v3.1.0 ✅"]
    A --> D["v3.9.0 ✅"]
    A --> E["v4.0.0 ❌\n(Major bump)"]

    style A fill:#3b82f6,stroke:#1e40af,color:#fff
    style B fill:#10b981,stroke:#059669,color:#fff
    style C fill:#10b981,stroke:#059669,color:#fff
    style D fill:#10b981,stroke:#059669,color:#fff
    style E fill:#ef4444,stroke:#dc2626,color:#fff

Strategi Upgrade yang Aman #

Mengupgrade versi module, terutama untuk MAJOR version, perlu dilakukan dengan hati-hati.

# WORKFLOW UPGRADE MODULE:

# 1. Baca CHANGELOG versi baru — cari breaking change
# 2. Test di environment dev/staging dulu
# 3. Jalankan terraform plan setelah update versi

# Perubahan di konfigurasi:
# Dari:
# source = "git::...?ref=v1.2.0"
# Ke:
# source = "git::...?ref=v2.0.0"

# 4. Baca output plan dengan cermat
terraform plan
# Perhatikan resource mana yang akan di-replace
# Breaking change di module sering menyebabkan resource di-replace

# 5. Jika ada replace yang tidak diinginkan, pertimbangkan:
#    - Apakah perlu moved block untuk rename resource?
#    - Apakah perlu tahap migrasi sebelum upgrade penuh?
# Contoh: module merename resource internal dari v1 ke v2
# Tanpa moved block, Terraform akan destroy + create

# Di konfigurasi root (bukan di module):
moved {
  from = module.vpc.aws_subnet.public
  to   = module.vpc.aws_subnet.public_tier  # Nama baru di module v2
}

Mengelola Breaking Change sebagai Penulis Module #

# STRATEGI 1: Deprecation dengan backward compatibility sementara
# Beri masa transisi sebelum benar-benar hapus variable/output lama

variable "subnet_id" {
  description = "DEPRECATED: Gunakan subnet_ids. Akan dihapus di v3.0."
  type        = string
  default     = null

  validation {
    condition     = var.subnet_id == null
    error_message = "subnet_id sudah deprecated, gunakan subnet_ids (list)."
    # Atau biarkan berjalan tapi log warning via null_resource
  }
}

variable "subnet_ids" {
  description = "List ID subnet. Gantikan subnet_id yang deprecated."
  type        = list(string)
  default     = []
}

locals {
  # Kompatibilitas backward: jika subnet_id diisi, jadikan list
  effective_subnet_ids = (
    length(var.subnet_ids) > 0
    ? var.subnet_ids
    : var.subnet_id != null ? [var.subnet_id] : []
  )
}


Semantic Versioning dalam Praktik #

Semantic versioning (MAJOR.MINOR.PATCH) sangat penting untuk module yang dipublikasikan. Setiap perubahan harus diklasifikasikan dengan benar.

# PATCH (x.y.Z): Perbaikan bug yang tidak mengubah interface
# Contoh: fix typo di description, perbaiki tag yang salah
# Tidak ada breaking change → consumer tidak perlu ubah apapun
git tag v1.2.1

# MINOR (x.Y.z): Fitur baru yang backward-compatible
# Contoh: tambah output baru, tambah optional variable baru
# Consumer existing tidak terpengaruh → bisa upgrade dengan aman
git tag v1.3.0

# MAJOR (X.y.z): Breaking change
# Contoh: hapus variable, ubah tipe output, hapus resource
# Consumer HARUS review dan update → upgrade perlu perencanaan
git tag v2.0.0
flowchart TD
    A["Perubahan
di module"] --> B{"Breaking
change?"}
    B -->|"Ya"| C["MAJOR
version bump
(X.0.0)"]
    B -->|"Tidak"| D{"Fitur
baru?"}
    D -->|"Ya"| E["MINOR
version bump
(x.Y.0)"]
    D -->|"Tidak"| F{"Bug fix?"}
    F -->|"Ya"| G["PATCH
version bump
(x.y.Z)"]
    F -->|"Tidak"| H["Tidak perlu
bump"]

    style A fill:#e3f2fd,stroke:#1565c0
    style C fill:#ffebee,stroke:#c62828
    style E fill:#fff3e0,stroke:#e65100
    style G fill:#e8f5e9,stroke:#2e7d32

Version Constraint Patterns #

# Eksak — hanya versi tertentu (terlalu ketat untuk production)
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"  # Tidak akan pernah auto-update
}

# Pessimistic — patch update saja (recommended untuk stability)
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "~> 5.1"  # >= 5.1.0, < 5.2.0
}

# Minor update — termasuk minor dan patch
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = ">= 5.1.0, < 6.0.0"  # Semua 5.x tapi bukan 6.0
}

Version Constraint Best Practices #

# Untuk module internal (di kontrol kamu sendiri):
module "app" {
  source  = "./modules/app"
  version = "~> 2.1"  # Boleh patch update
}

# Untuk module eksternal (tidak kamu kontrol):
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"  # Lebih ketat — hanya patch
}

# Untuk provider (stabilitas sangat penting):
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Pessimistic ~> untuk stability
    }
  }
}
# Cek versi yang tersedia
terraform providers lock -platform=linux_amd64
# Generate lock file untuk konsistensi cross-platform

# Update provider ke versi terbaru
terraform init -upgrade
# Hati-hati: bisa mengubah behavior

Pinning Provider Versions #

# Di dalam module, JANGAN pin provider version
# Biarkan root module yang menentukan

# Module (modules/networking/main.tf):
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      # JANGAN tentukan version di sini
      # Biarkan root module menentukan
    }
  }
}

# Root module (main.tf):
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Version ditentukan di root
    }
  }
}
# .terraform.lock.hcl — commit ke version control
# File ini memastikan semua developer dan CI/CD
# menggunakan versi provider yang sama

# Generate lock file untuk multiple platforms
terraform providers lock   -platform=linux_amd64   -platform=darwin_arm64   -platform=windows_amd64

# Update provider ke versi terbaru
terraform init -upgrade

# Review perubahan lock file
git diff .terraform.lock.hcl

Version Constraint Operators #

# OPERATORS:
# =  (exact):    version = "5.0.0"
# != (exclude):  version != "5.0.0"
# >  (gt):       version > "5.0.0"
# >= (gte):      version >= "5.0.0"
# <  (lt):       version < "6.0.0"
# <= (lte):      version <= "5.5.0"
# ~> (pessimistic): version ~> "5.0" (allows 5.x but not 6.0)

# CONTOH:
# ~> 5.0  = >= 5.0, < 6.0
# ~> 5.1  = >= 5.1, < 6.0
# >= 5.0, < 6.0  = sama dengan ~> 5.0

# BEST PRACTICE:
# Module di registry:  gunakan ~> untuk minor version
# Provider:            gunakan ~> untuk major version
# Internal module:     pin exact version

Ringkasan #

  • Versioning adalah kebutuhan, bukan pilihan — tanpa versi, dua tim yang apply di waktu berbeda bisa mendapat hasil yang berbeda.
  • Semantic versioning: PATCH untuk bug fix, MINOR untuk fitur backward-compatible, MAJOR untuk breaking change.
  • Git tag sebagai versi untuk module di Git repo — ?ref=v1.2.0 di source URL.
  • version constraint di Terraform Registry — gunakan ~> untuk fleksibilitas patch/minor, = untuk pin ketat di production.
  • Jangan pernah module tanpa version constraint untuk source dari Registry — setiap terraform init bisa mendapat versi berbeda.
  • Upgrade major version secara bertahap — test di dev/staging dulu, baca output plan dengan cermat, gunakan moved block jika ada rename resource internal.

← Sebelumnya: Root vs Child Module   Berikutnya: Interface Design →

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