Condition & Loop

Condition & Loop #

Otomatisasi infrastruktur yang cerdas tidak boleh berjalan secara kaku dan linear. Di lingkungan produksi yang nyata, kita akan selalu menghadapi variasi kondisi server target. Kita mungkin perlu menginstal paket perangkat lunak tertentu hanya di sistem operasi CentOS, melewati (skip) tugas pembuatan database jika layanan database belum terinstal, atau mengulang instalasi daftar pustaka yang panjang pada server web tanpa menuliskan baris kode yang sama berulang kali.

Untuk menangani dinamika ini, Ansible menyediakan dua fitur kontrol alur yang sangat penting: kondisional (when) dan iterasi (loop). Memahami cara menggabungkan evaluasi logika kondisional Jinja2 dengan kontrol perulangan tingkat lanjut akan memberikan kita fleksibilitas penuh untuk merancang playbook yang adaptif, aman, dan efisien. Artikel ini akan membedah sintaksis kondisional, menjejalkan parameter iterasi modern, serta mengupas teknik optimasi output perulangan.


Logika Kondisional dengan when #

Kata kunci when digunakan untuk menentukan apakah suatu task akan dijalankan atau dilewati oleh Ansible untuk host tertentu. Kondisi yang ditulis di dalam when dievaluasi sebagai ekspresi Jinja2 yang mengembalikan nilai boolean (true atau false).

Aturan Emas: Bebas Kurung Kurawal Ganda (No Curly Braces) #

Salah satu kesalahan paling umum yang dilakukan oleh pemula (dan bahkan developer berpengalaman) adalah menuliskan kurung kurawal ganda {{ }} di dalam baris pernyataan when.

Kita harus mengingat aturan dasar ini: pernyataan when sudah berada di dalam lingkungan evaluasi Jinja2 secara implisit. Menambahkan {{ }} di dalam when adalah sebuah anti-pattern yang akan memicu kesalahan pembacaan (syntax parser error) atau perilaku evaluasi variabel yang tidak terduga pada versi Ansible modern.

Mari kita bandingkan contoh penulisan yang salah dan benar di bawah ini:

# ANTI-PATTERN: Menuliskan kurung kurawal ganda di dalam when
- name: Menginstal paket utilitas (Cara Salah)
  apt:
    name: curl
    state: present
  when: "{{ ansible_os_family == 'Debian' }}"
  # ✗ Ansible akan mengevaluasi kurung kurawal secara ganda,
  # yang dapat memicu error parser pada versi Ansible 2.10 ke atas.
# BENAR: Menuliskan ekspresi kondisional secara bersih tanpa kurung kurawal
- name: Menginstal paket utilitas (Cara Benar)
  apt:
    name: curl
    state: present
  when: ansible_os_family == 'Debian'
  # ✓ Variabel dibaca secara langsung karena modul when mengevaluasi string sebagai ekspresi Jinja2 native.

Evaluasi Kondisi Berbasis Facts Populer #

Dalam merancang ekspresi kondisional, kita sering kali mengandalkan data karakteristik sistem yang dikumpulkan secara otomatis oleh Ansible pada awal play melalui proses gathering facts.

Berikut adalah tabel referensi variabel facts populer yang sering kita jadikan parameter kondisi di lingkungan produksi:

Nama Variabel Facts Jenis Data Contoh Nilai Deskripsi Penggunaan
ansible_os_family String Debian, RedHat, Suse Mengelompokkan tugas berdasarkan keluarga distribusi Linux utama.
ansible_distribution String Ubuntu, CentOS, Rocky Menentukan aksi spesifik untuk distribusi tertentu.
ansible_distribution_major_version String 20, 22, 8, 9 Memeriksa versi rilis utama (misalnya untuk konfigurasi khusus RHEL 9).
ansible_memtotal_mb Integer 2048, 16384 Mengatur parameter alokasi memori aplikasi berdasarkan RAM fisik server.
ansible_processor_vcpus Integer 2, 4, 16 Menentukan jumlah worker process (seperti worker_processes di Nginx).
ansible_virtualization_type String kvm, docker, virtualbox Menghindari instalasi modul kernel tertentu jika berada di dalam container.
ansible_architecture String x86_64, aarch64 Memilih arsitektur paket biner aplikasi yang tepat (seperti ARM vs Intel).

Berikut adalah contoh implementasi kondisional memanfaatkan tabel facts di atas untuk mengoptimalkan konfigurasi kernel dan paket:

# Mengatur alokasi memori swap hanya untuk server dengan spesifikasi rendah
- name: Membuat file swap tambahan
  command: /usr/local/bin/create_swap.sh
  when: 
    - ansible_memtotal_mb < 4096
    - ansible_virtualization_type != 'docker'
  # ✓ Swap tidak akan dibuat jika RAM >= 4GB atau jika server berjalan di dalam container Docker.

Ansible mendukung penggunaan operator logika standar untuk menggabungkan beberapa kriteria evaluasi kondisional.

1. Logika AND (Semua Kondisi Harus Terpenuhi) #

Ada dua cara menuliskan logika AND di dalam when. Kita bisa menggunakan operator and eksplisit dalam satu baris, atau menuliskannya sebagai format daftar (list) YAML. Format daftar YAML sangat direkomendasikan karena membuat baris kode kita jauh lebih rapi dan mudah dibaca.

# Format List YAML untuk Logika AND (Sangat Direkomendasikan)
- name: Mengonfigurasi modul kernel khusus virtualisasi Intel
  modprobe:
    name: kvm_intel
    state: present
  when:
    - ansible_os_family == 'RedHat'
    - ansible_architecture == 'x86_64'
    - ansible_virtualization_type == 'kvm'
  # ✓ Semua tiga kondisi di atas wajib bernilai true agar task ini dijalankan.

2. Logika OR (Salah Satu Kondisi Terpenuhi) #

Untuk menggunakan logika OR, kita menuliskan operator or di dalam baris ekspresi kondisional.

# Menggunakan operator OR
- name: Memasang utilitas jaringan tambahan
  apt:
    name: net-tools
    state: present
  when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
  # ✓ Task akan berjalan jika distribusi adalah Ubuntu, Debian, atau keduanya.

3. Logika NOT (Negasi/Kebalikan) #

Kita bisa menggunakan operator not untuk membalikkan hasil evaluasi boolean.

# Menggunakan operator NOT
- name: Menghapus file sementara untuk server non-production
  file:
    path: /tmp/debug_logs.txt
    state: absent
  when: not (env == 'production')
  # ✓ File akan dihapus hanya jika variabel env TIDAK bernilai 'production'.

4. Menulis Kondisi Multi-baris yang Kompleks #

Jika kita harus menggabungkan operator AND dan OR dengan pengelompokan tanda kurung yang panjang, gunakan indikator blok scalar YAML when: > agar kode tetap rapi tanpa scroll horizontal:

# Blok scalar untuk kondisi multi-baris
- name: Menerapkan patch keamanan kritis
  apt:
    name: security-patch
    state: latest
  when: >
    (ansible_distribution == 'Ubuntu' and ansible_distribution_version == '22.04')
    or
    (ansible_os_family == 'RedHat' and ansible_distribution_major_version | int >= 8)    

Iterasi Modern: loop vs with_items (Legacy) #

Sebelum versi Ansible 2.5 dirilis, developer menggunakan berbagai varian kata kunci berawalan with_ (seperti with_items, with_dict, with_subelements) untuk melakukan perulangan. Sejak versi 2.5 ke atas, Ansible menyatukan seluruh fungsi perulangan tersebut ke dalam satu kata kunci modern: loop.

Meskipun with_items masih didukung demi kompatibilitas ke belakang (backward compatibility), menggunakan with_items pada proyek baru dikategorikan sebagai tindakan yang kurang tepat (legacy/deprecated).

Mengapa Memilih loop? #

  • Sederhana: loop hanya menerima list datar (flat list) satu dimensi secara langsung. Hal ini membuat perilakunya sangat mudah diprediksi.
  • Deklaratif: Kita dapat menggabungkan loop dengan berbagai filter Jinja2 (seperti flatten, dict2items, atau subelements) untuk memanipulasi struktur data sebelum diiterasi. Hal ini memisahkan logika perulangan dengan pemrosesan data secara bersih.

Mari kita lihat perbandingan penulisan kodenya:

# LEGACY (with_items): Gaya lama yang tidak direkomendasikan
- name: Membuat beberapa direktori proyek (Gaya Lama)
  file:
    path: "{{ item }}"
    state: directory
  with_items:
    - /var/www/app1
    - /var/www/app2

# MODERN (loop): Gaya baru yang sangat direkomendasikan
- name: Membuat beberapa direktori proyek (Gaya Modern)
  file:
    path: "{{ item }}"
    state: directory
  loop:
    - /var/www/app1
    - /var/www/app2

Mengontrol Output Iterasi dengan loop_control #

Ketika kita melakukan perulangan pada daftar yang berisi struktur data kompleks seperti dictionary (kamus data), visualisasi keluaran pada layar terminal (stdout) akan menampilkan seluruh isi dictionary tersebut untuk setiap iterasi. Ini akan membuat log eksekusi playbook kita menjadi sangat panjang, kotor, dan sulit dibaca.

Ansible menyediakan parameter loop_control untuk mengendalikan perilaku visual dan fungsional dari proses iterasi kita.

1. Merapikan Output dengan label #

Gunakan opsi label untuk menentukan informasi ringkas apa saja dari properti dictionary yang ingin kita tampilkan di stdout terminal.

# Menggunakan loop_control untuk menyaring visualisasi log terminal
- name: Membuat akun pengguna sistem dengan parameter lengkap
  user:
    name: "{{ item.username }}"
    uid: "{{ item.uid }}"
    shell: "{{ item.shell }}"
    state: present
  loop:
    - { username: 'john', uid: 2001, shell: '/bin/bash', comment: 'Lead Developer' }
    - { username: 'jane', uid: 2002, shell: '/bin/zsh', comment: 'SecOps Engineer' }
  loop_control:
    label: "{{ item.username }} (UID: {{ item.uid }})"
  # ✓ Output terminal hanya akan menampilkan: item=john (UID: 2001) dan item=jane (UID: 2002).
  # Detail properti sensitif atau panjang lainnya tidak akan mengotori layar.

2. Melacak Indeks Perulangan dengan index_var #

Terkadang, di dalam iterasi kita membutuhkan nomor urut index (dimulai dari 0). Kita bisa mendaftarkan variabel penampung index menggunakan opsi index_var.

# Melacak index baris untuk penomoran file
- name: Menyalin konfigurasi dengan penomoran unik
  template:
    src: virtualhost.conf.j2
    dest: "/etc/nginx/sites-enabled/{{ idx + 1 }}-{{ item }}.conf"
  loop:
    - frontend
    - backend
    - api
  loop_control:
    index_var: idx
  # ✓ File yang terbentuk akan memiliki prefix angka urut: 1-frontend.conf, 2-backend.conf, dst.

3. Menghindari Tabrakan Namespace dengan loop_var (Nested Loop) #

Ketika kita menulis perulangan bersarang (nested loops) menggunakan include_tasks, variabel bawaan item pada perulangan luar akan tertimpa oleh variabel item pada perulangan dalam. Hal ini akan menyebabkan error pembacaan variabel.

Untuk mencegah bentrokan ini, kita wajib mendefinisikan ulang nama variabel iterasi menggunakan opsi loop_var.

# ==============================================================================
# BERKAS: main.yml (Playbook Utama)
# ==============================================================================
- name: Melakukan konfigurasi virtual host per port
  include_tasks: configure_ports.yml
  loop:
    - { domain: 'app.example.com', directory: '/var/www/app' }
    - { domain: 'api.example.com', directory: '/var/www/api' }
  loop_control:
    loop_var: site_item
  # ✓ Kita mengganti nama variabel default 'item' menjadi 'site_item' untuk loop tingkat pertama.
# ==============================================================================
# BERKAS: configure_ports.yml (Task yang disertakan)
# ==============================================================================
- name: Mengaktifkan port listen untuk domain {{ site_item.domain }}
  ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  loop:
    - 80
    - 443
  # ✓ Di sini kita aman menggunakan variabel 'item' bawaan untuk loop dalam
  # tanpa takut mengganggu variabel 'site_item' milik loop luar.

Kombinasi: Evaluasi when di dalam loop #

Satu skenario yang sangat sering kita gunakan adalah menggabungkan pernyataan when dengan pernyataan loop. Ketika kedua kata kunci ini ditempelkan pada satu task yang sama, penting untuk memahami alur evaluasi internal Ansible.

[!IMPORTANT] Ansible mengevaluasi kondisi when untuk setiap item di dalam loop secara individual (satu per satu), bukan mengevaluasi loop secara keseluruhan. Ini berarti perulangan tetap berjalan untuk seluruh item, tetapi task hanya akan memproses item yang memenuhi kriteria di dalam when.

Berikut adalah visualisasi alur pengambilan keputusan evaluasi per-item di dalam perulangan kondisional:

flowchart TD
    Start(["Mulai Iterasi Loop"]) --> GetItem["Ambil Item X dari Daftar Loop"]
    GetItem --> EvalCondition{"Evaluasi Kondisi 'when' terhadap Item X"}
    
    EvalCondition -- "True" --> ExecTask["Jalankan Aksi Modul untuk Item X"]
    ExecTask --> CheckNext{"Ada Item Berikutnya?"}
    
    EvalCondition -- "False" --> SkipItem["Lewati (Skip) Item X"]
    SkipItem --> CheckNext
    
    CheckNext -- "Ya" --> GetItem
    CheckNext -- "Tidak" --> EndLoop(["Selesai Iterasi Loop"])

Mari kita lihat contoh implementasinya di bawah ini. Kita ingin membuat direktori proyek, tetapi hanya direktori yang memiliki bendera properti active: true saja yang akan dibuat.

# Penerapan kondisional per-item di dalam loop
- name: Membuat direktori proyek yang aktif
  file:
    path: "{{ item.path }}"
    state: directory
    owner: deployer
    group: deployer
    mode: '0755'
  loop:
    - { path: '/var/www/prod_site', active: true }
    - { path: '/var/www/staging_site', active: false }
    - { path: '/var/www/dev_site', active: true }
  when: item.active
  # ✓ Hasil eksekusi: direktori prod_site dan dev_site akan dibuat,
  # sedangkan staging_site akan dilewati dengan status 'skipped'.

Ringkasan #

  • Jinja2 Tanpa Kurung Kurawal: Hindari penulisan {{ }} di dalam opsi when. Pernyataan kondisional Ansible mengevaluasi variabel secara langsung.
  • Variabel Facts: Manfaatkan tabel facts sistem seperti ansible_os_family atau ansible_memtotal_mb untuk menulis kondisi adaptif berdasarkan spesifikasi server.
  • Logika Bersih: Gunakan format daftar YAML untuk logika AND yang kompleks agar kode playbook kita mudah dibaca secara vertikal.
  • Standarisasi Loop: Gunakan kata kunci modern loop alih-alih with_items lama demi menjaga kompatibilitas masa depan dan kerapian sintaksis.
  • Label Output: Gunakan loop_control.label untuk membatasi panjang log eksekusi terminal saat melakukan perulangan pada list dictionary.
  • Looping Bersarang: Gunakan opsi loop_control.loop_var pada nested loop untuk menghindari tabrakan namespace variabel item.
  • Evaluasi Per Item: Pemasangan when di dalam loop memicu evaluasi kondisi pada setiap item satu per satu, bukan menyaring atau menghentikan seluruh perulangan.

← Sebelumnya: Notify & Listen   Berikutnya: Scope & Precedence →

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