Belajar Terraform - Advanced State Manipulation (import, moved, state)
Episode 8 of 21

Belajar Terraform - Advanced State Manipulation (import, moved, state)

Dalam episode ini kita akan menghubungkan infrastruktur eksisting ke Terraform dengan import block, merestrukturisasi kode tanpa destroy menggunakan moved, serta menginspeksi dan membersihkan state melalui perintah terraform state.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Pendahuluan

Setelah di episode 7 sebelumnya kita membahas Resource Dependencies & Lifecycle Rules — implicit vs explicit dependencies (depends_on), serta lifecycle rules seperti create_before_destroy, prevent_destroy, dan ignore_changes — pada episode kali ini kita akan masuk ke medan yang sangat sering dihadapi praktisi di lapangan: berurusan dengan state file secara langsung.

Mari kita bicara jujur: hampir tidak ada tim yang memulai project IaC dari nol. Pada kenyataannya, infrastruktur sudah berdiri lebih dulu — dibangun lewat console, script lama, atau tangan tim lain yang sudah resign. Lalu datang sebuah project baru: "migrasikan semuanya ke Terraform." Pertanyaan pertama yang muncul: apakah kita harus menghancurkan semuanya dan membuat ulang? Jawabannya tegas: tidak.

Di sisi lain, kode juga terus berkembang. Resource yang tadinya bernama aws_instance.web mungkin harus dipindah ke dalam module, atau di-rename menjadi aws_instance.nginx. Kalau kita menghapus resource lama dan menulis ulang yang baru, Terraform akan menghancurkan dan membuat ulang — sesuatu yang sama sekali tidak kita inginkan.

Di episode inilah kalian akan menguasai senjata pamungkas manajemen state: import, moved, dan keluarga besar terraform state. Ketiganya memungkinkan kita mengubah cara pandang Terraform terhadap infrastruktur — tanpa menghancurkan apa pun di cloud.

Pembahasan Utama

Menghubungkan Infrastruktur Eksisting ke Terraform

Konsep dasarnya bernama importing: memasukkan objek yang sudah ada di cloud ke dalam state file Terraform, sehingga Terraform mulai "mengakui" dan mengelolanya. Penting untuk dipahami sejak awal: import hanya menulis ke state, tidak membuat resource, dan tidak menulis kode konfigurasi untuk kita.

Ada dua cara melakukan import: pendekatan lama berbasis CLI, dan pendekatan modern yang deklaratif.

Legacy: terraform import (CLI)

Sebelum Terraform 1.5, satu-satunya cara adalah menjalankan perintah di terminal. Sintaksnya sederhana:

plaintext
terraform import <address> <id>

Dimana <address> adalah address resource di kode (misal aws_instance.web) dan <id> adalah ID objek di cloud (misal i-0abcd1234efgh5678). Praktiknya:

terraform import (legacy)
terraform import aws_instance.web i-0abcd1234efgh5678
Output import
aws_instance.web: Importing from ID "i-0abcd1234efgh5678"...
aws_instance.web: Import prepared!
  Prepared aws_instance.web for import
aws_instance.web: Refreshing state... [id=i-0abcd1234efgh5678]
 
Import successful!
 
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Beberapa karakteristik pendekatan CLI yang perlu dicatat:

  • Tidak menuliskan kode. Resource block harus sudah ada di konfigurasi sebelum import; jika tidak, plan setelahnya akan mencoba menghancurkan resource tersebut.
  • Satu perintah untuk satu resource — mengimpor ratusan resource berarti ratusan perintah.
  • Tidak idempotent dalam satu run: jika ada yang gagal di tengah, kalian harus menjalankan ulang sisanya.
  • Informasi ID harus diketahui manual (dari console, awscli, dsb.).

Modern: import Block (Terraform 1.5+)

Mulai Terraform 1.5, import bisa ditulis deklaratif di dalam kode sebagai blok import, berdampingan dengan definisi resource. Bentuknya:

import-block.tf
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
 
  tags = {
    Name = "web-server"
  }
}
 
import {
  to = aws_instance.web
  id = "i-0abcd1234efgh5678"
}

Yang membuat pendekatan ini jauh lebih unggul:

  • Blok import hidup berdampingan dengan resource block-nya — satu source of truth.
  • Menjalankan terraform plan saja sudah cukup untuk memverifikasi; plan akan menampilkan "Plan: 1 to import".
  • Import dieksekusi oleh terraform apply, dan blok import bisa dibiarkan atau dihapus setelah sukses.
  • Lebih mudah di-review dalam Pull Request — siapa mengimpor apa, tercatat di git.

Output terraform plan untuk config di atas akan terlihat seperti ini:

Output terraform plan
Terraform will perform the following actions:
 
  # aws_instance.web will be imported
    resource "aws_instance" "web" {
      ami            = "ami-0abcdef1234567890"
      id             = "i-0abcd1234efgh5678"
      instance_type  = "t3.micro"
      ...
    }
 
Plan: 1 to import.

Alur Kerja Import yang Benar

Meski mekanismenya berbeda, alur kerjanya sama dan wajib kalian hafal:

  1. Tulis resource block yang merepresentasikan objek eksisting (atribut minimal, setidaknya yang menjadi identity seperti bucket, name, ami).
  2. Deklarasikan import — via blok import (modern) atau perintah terraform import (legacy).
  3. Jalankan terraform plan untuk memverifikasi: Plan: 1 to import berarti bersih. Jika plan menampilkan 1 to change, berarti konfigurasi kalian belum cocok dengan keadaan nyata — sesuaikan kode, jangan buru-buru apply.
  4. Jalankan terraform apply untuk menyelesaikan import.

Important

Kesalahan paling fatal: menjalankan terraform import tanpa menulis resource block terlebih dahulu. Import berhasil masuk ke state, tapi karena tidak ada blok yang cocok, terraform plan berikutnya akan menampilkan "Plan: 1 to destroy". Terraform mengira resource itu sudah tidak dideklarasikan — dan apply akan menghancurkannya di cloud. Selalu tulis kode sebelum import, dan selalu cek plan sebelum apply.

Perbandingan: Legacy vs Modern

Aspekterraform import (CLI)import block (deklaratif)
Ditulis diTerminalKode konfigurasi (.tf)
Butuh Terraformsemua versi1.5+
Dapat di-review via gittidakya
Import massalsatu per satusekaligus di apply
Idempotenttidak otomatisya
Verifikasimanual / plan berikutnyalangsung di plan
Rekomendasikasus ad-hoc/scriptingdefault project baru
terraform import aws_s3_bucket.data "my-existing-bucket"

Refactoring Kode Tanpa Destroy Resource

Setelah infrastruktur diimport, kode terus berevolusi: resource di-rename, dipindah ke module, atau digabung dengan resource lain. Naifnya, jika kita hanya mengubah nama di kode, Terraform akan menafsirkannya sebagai hapus yang lama, buat yang baru — destroy dan recreate. Ada dua mekanisme untuk menghindari itu.

moved Block: Refactoring yang Tertulis di Kode

Terraform 1.1 memperkenalkan blok moved — cara deklaratif untuk memberi tahu Terraform: "resource di address lama sekarang tinggal di address baru, perlakukan keduanya sama."

moved-block.tf
resource "aws_instance" "nginx" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}
 
moved {
  from = aws_instance.web
  to   = aws_instance.nginx
}

Saat menjalankan terraform plan, output akan menampilkan relokasi dengan jelas:

Output terraform plan
aws_instance.nginx: Refreshing state... [id=i-0abcd1234efgh5678]
 
Moved:
  aws_instance.web aws_instance.nginx
 
Plan: 0 to add, 0 to change, 0 to destroy.

Perhatikan Plan: 0 to destroy — resource tidak dihancurkan, hanya "dipindah" di dalam state. Setelah apply sukses, blok moved boleh dihapus dari kode (Terraform bahkan menyarankan pembersihan di output apply). Menyimpannya lebih lama juga tidak berbahaya — blok ini hanya aktif selama address lama masih dirujuk di state, dan bisa menimbulkan kebingungan jika address itu dipakai lagi di masa depan.

terraform state mv: Refactoring Langsung di Terminal

Ada kalanya kita tidak bisa (atau tidak mau) mengubah kode lebih dulu — misalnya merestrukturisasi banyak resource dalam satu sesi, atau memindahkan resource antar state file. Untuk kasus itu, terraform state mv bekerja langsung di terminal:

terraform state mv
terraform state mv aws_instance.web aws_instance.nginx
plaintext
Move "aws_instance.web" to "aws_instance.nginx"
Successfully moved 1 item(s).

terraform state mv juga bekerja untuk memindahkan resource ke dalam module atau sebaliknya, serta antar state backend:

Contoh lain state mv
terraform state mv 'module.web.aws_instance.app' 'aws_instance.legacy'
terraform state mv -state=old.tfstate -state-out=new.tfstate module.db.aws_db_instance.primary module.db.aws_db_instance.primary
moved {
  from = aws_instance.web
  to   = aws_instance.nginx
}

Tip

Aturan memilih: moved block saat refactoring adalah bagian dari perubahan kode yang akan di-review (default project, kode versioned), terraform state mv saat tindakan state-nya bersifat sekali jalan, otomatis/scripted, atau melibatkan pemindahan antar state file. Setelah state mv, ingat untuk memperbarui kode konfigurasi ke address baru — jika tidak, plan akan menganggap resource lama hilang dan mencoba membuat ulang.

Pembersihan State & Inspeksi

Keluarga terraform state juga menyediakan perintah untuk melihat isi state dan melepaskan resource dari manajemen Terraform tanpa menyentuh cloud.

terraform state list

Menampilkan seluruh address resource yang terdaftar di state — cara tercepat untuk melihat "siapa yang dikelola Terraform saat ini":

terraform state list
terraform state list
Output state list
aws_instance.nginx
aws_security_group.web
aws_subnet.app
aws_vpc.main
module.db.aws_db_instance.primary

terraform state show

Menampilkan detail atribut satu resource tertentu — semacam state list yang dikombinasikan dengan inspeksi mendalam:

terraform state show
terraform state show aws_instance.nginx
Output state show
# aws_instance.nginx:
resource "aws_instance" "nginx" {
    ami                    = "ami-0abcdef1234567890"
    arn                    = "arn:aws:ec2:ap-southeast-1:123456789012:instance/i-0abcd1234efgh5678"
    id                     = "i-0abcd1234efgh5678"
    instance_type          = "t3.micro"
    private_dns            = "ip-10-0-1-5.ap-southeast-1.compute.internal"
    private_ip             = "10.0.1.5"
    public_ip              = "203.0.113.10"
    subnet_id              = "subnet-0abc123"
    vpc_security_group_ids = ["sg-0def456"]
}

terraform state rm

Melepaskan resource dari state tanpa menghapusnya di cloud. Setelah perintah ini, Terraform tidak lagi mengelola resource tersebut — ia menjadi "liar" (orphan) dari perspektif IaC, tapi tetap hidup dan membebankan biaya.

terraform state rm
terraform state rm aws_instance.nginx
plaintext
Removed aws_instance.nginx
Successfully removed 1 resource instance(s).

Skenario penggunaan yang sah:

  • Resource sengaja diserahkan kembali ke pengelolaan manual (misalnya bucket lama yang diserahkan ke tim lain).
  • Menghapus resource yang tidak sengaja ter-import.
  • Menurunkan resource secara terkontrol: prevent_destroy + state rm + hapus kode.

terraform refresh dan -refresh-only

terraform refresh memperbarui atribut state sesuai kondisi nyata di cloud tanpa mengubah kode dan tanpa plan. Karena refresh sebenarnya sudah dipakai juga oleh plan/apply, perintah ini sekarang lebih dianjurkan diganti dengan terraform plan -refresh-only yang outputnya lebih aman untuk ditinjau:

refresh-only
terraform plan -refresh-only

Tabel Perintah terraform state

SubcommandFungsi
terraform state listMenampilkan semua address resource dalam state
terraform state showMenampilkan detail atribut resource tertentu
terraform state mvMemindahkan resource antar address / state file
terraform state rmMenghapus resource dari state tanpa destroy di cloud
terraform state pullMengambil & mencetak raw state (JSON) dari backend
terraform state pushMenimpa state backend dengan file yang di-pull (berbahaya)
terraform state replace-providerMengganti nama provider di state (mis. saat migrasi provider)

Kesalahan Umum (Common Pitfalls)

1. Import tanpa resource block → resource jadi "to be destroyed"

Sudah dibahas di callout sebelumnya, tapi layak diulang: ini penyebab paling sering "infrastruktur hilang setelah import". Selalu tulis resource block, plan, baru apply.

2. ID import salah → error membingungkan

Import dengan ID yang tidak valid menghasilkan pesan seperti Error: Cannot import non-existent remote object atau The given ID "..." is invalid. Sebelum import, verifikasi ID dari konsol cloud atau command-line tooling (aws ec2 describe-instances, aws s3api list-buckets, dll.).

3. state mv yang tidak diikuti pembaruan kode

Memindah address di state tapi lupa mengubah kode → plan berikutnya menampilkan 0 to add, 1 to destroy untuk address lama. Selalu pasangkan state mv dengan edit kode yang sesuai.

4. state rm yang tidak disengaja = biaya tersembunyi

Resource yang di-rm tidak lagi dihitung oleh Terraform, tapi tetap berjalan dan ditagih di cloud. Pastikan state rm dilakukan dengan sengaja dan tercatat — misalnya lewat perubahan kode yang jelas, bukan kebiasaan "kalau error, hapus saja dari state".

5. Mengedit file state JSON secara manual

terraform state pull lalu mengubah JSON dengan editor lalu state push adalah praktik yang hampir selalu berakhir buruk — struktur JSON mudah rusak, dan push menimpa state backend tanpa pengaman. Selalu gunakan subcommand resmi (state mv, state rm, dll.). Simpan state pull untuk backup/investigasi, bukan untuk diedit.

6. Blok import yang dibiarkan setelah sukses

Setelah blok import dieksekusi sukses, hapus blok tersebut. Membiarkannya di kode akan membuat apply berikutnya mencoba import ulang resource yang sudah ada — umumnya tidak berbahaya (no-op), tapi berisiko jika ID objek berubah.

Penutup

Pada episode 8 ini kita telah menguasai operasi tingkat lanjut terhadap state file: importing infrastruktur eksisting lewat perintah legacy terraform import maupun blok deklaratif import (Terraform 1.5+) dengan alur kerja tulis-kode → import → plan → apply; refactoring tanpa destroy menggunakan moved block dan terraform state mv; serta inspeksi & pembersihan state melalui terraform state list, state show, dan state rm.

Kemampuan inilah yang membuat Terraform bisa dipakai bukan hanya untuk project greenfield, tapi juga untuk menaklukkan infrastruktur warisan yang sudah berdiri bertahun-tahun. Migrasi tanpa destroy, refactoring tanpa downtime, dan kendali penuh atas isi state — inilah tanda kematangan seorang practitioner IaC.

Namun mengelola ratusan resource dalam satu file tunggal yang panjang bukanlah praktik yang berkelanjutan. Bagaimana caranya mengemas konfigurasi menjadi komponen yang bersih dan dapat dipakai ulang? Di episode 9 selanjutnya kita akan membahas Membuat & Mengelola Reusable Modules — struktur standar modul (main.tf, variables.tf, outputs.tf), local vs remote module, dan bagaimana modul menjaga kode tetap DRY. Pastikan tetap semangat!