Skip to content

Dry Run Ansible Crash di Task Report, Bukan di Task Utamanya

Adityo Guni Waluyo

Task command di-skip saat --check, registered var jadi undefined, dan filter default di ujung chain nggak sempat nolong. Tempel default-nya dekat sumbernya.

Ringkasan

Dry run Ansible --check gagal gara-gara task debug di akhir, padahal biang keroknya variabel register yang nggak keisi karena task sebelumnya ke-skip. default('?') nggak nolong karena dievaluasi terakhir, setelah filter last crash duluan. Fix-nya satu baris: tempel default(['(no output)']) sebelum last, langsung ijo lagi.

Dry run yang crash di task paling nggak penting

Saya ketik ansible-playbook upgrade-9router.yml --check buat ngecek dulu sebelum upgrade beneran. Ekspektasinya cuma simulasi, nggak ngubah apa pun di server. Eh yang crash malah task paling akhir, yang cuma ansible.builtin.debug buat nampilin ringkasan.

Padahal task-task sebelumnya yang command sama shell buat upgrade OS-nya anteng aja nggak error. Kok yang cuma nge-print malah bikin dry run gagal total.

Playbook-nya emang punya task Report di ujung kayak gini:

- name: Report
  ansible.builtin.debug:
    msg: "{{ inventory_hostname }} | {{ upgrade.stdout_lines | last | default('?') }} | http={{ health.stdout }}"

Niatnya simpel, abis upgrade mau liat output terakhir sama status http. Pas jalan normal, aman. Pas --check, langsung merah.

Yang ke-skip bukan Report-nya

Awalnya saya kira Report-nya yang error karena typo filter. Saya cek lagi, nggak ada yang salah. Ternyata yang bikin masalah bukan task Report itu sendiri.

Di check mode, Ansible itu emang jalan tanpa ngubah sistem remote sama sekali [1]. Buat modul yang nggak support check mode, perilakunya ya "report nothing and do nothing" [1]. command dan shell yang me-register upgrade sama health itu ke-skip di --check. Jadi variabel register-nya nggak pernah keisi.

Dokumentasinya nulis jelas, check mode "will not generate output for tasks that use conditionals based on registered variables (results of prior tasks)" [1]. Ini akar masalahnya. Variabelnya undefined bukan karena error, tapi karena task upstream emang sengaja nggak dieksekusi pas simulasi.

Error-nya baru meledak di task berikutnya yang baca register itu. Jadi Report yang keliatan salah, padahal biang keroknya ada di atasnya.

Saya sempet mikir default('?') di ujung udah cukup buat jaga-jaga. Ternyata nggak.

Filter di ujung chain nggak sempat nolong

Ini yang agak ngecoh sih. Saya pasang filter berantai dengan harapan kalo undefined bakal jatuh ke tanda tanya. Tapi chain filter itu dievaluasi kiri ke kanan.

upgrade.stdout_lines undefined, langsung dioper ke filter last. last nggak tau harus ngapain sama undefined, dia crash duluan. default('?') di ujung nggak pernah kesampaian dieksekusi.

Solusinya: default harus nempel sedekat mungkin ke nilai yang berpotensi undefined. Filter ini kerjanya simpel: "If the value is undefined it will return the passed default value, otherwise the value of the variable" [2]. Jadi kita kasih default dulu, baru di-last.

Fix-nya cuma satu baris di commit 120691a:

- name: Report
  ansible.builtin.debug:
    msg: "{{ inventory_hostname }} | {{ (upgrade.stdout_lines | default(['(no output)'])) | last }} | http={{ health.stdout | default('-') }}"

Untuk upgrade.stdout_lines saya bungkus dulu pakai default(['(no output)']) baru di-last. Jadi kalo pas --check dia undefined, yang di-last itu array default-nya, bukan undefined. Untuk health.stdout lebih simpel, cukup default('-') aja di ujung karena nggak ada last.

Pola ini emang idiom resmi di Ansible, dok-nya nyaranin "use the |default filter to provide an empty iterator" buat jaga variabel undefined [3]. Kasus loop atau report, polanya sama.

Itu doang. Dry run langsung ijo lagi.

Biar --check nggak nyusahin lagi

Abis kejadian ini saya baru ngeh ada toolkit kecil buat bikin dry run aman, semuanya ada di halaman check mode [1].

Kalo ada task yang emang harus tetap jalan walau --check, bisa pakai opsi check_mode per task, isi true atau false. Fitur ini udah ada sejak Ansible 2.2. Terus ada magic variable ansible_check_mode yang isinya true pas simulasi, enak buat kondisional: task-nya di-skip kalo lagi dry run, atau errornya diabaikan aja.

Kombinasi --check --diff juga enak buat liat perubahan file, udah ada sejak 2.4. Cuma output-nya bisa gede banget kalo fleet banyak, jadi dok-nya nyaranin pakai --limit satu host dulu pas nyoba.

Saya pribadi milih nggak over-engineering. Nggak semua task perlu dipaksa jalan pas check mode. Cukup pastiin task debug atau report di akhir itu kebal. Pendapat saya tegas di sini: setiap task report wajib nge-default semua registered var yang dibacanya. Nggak ada tawar-menawar. Lebih mending nampilin tanda strip atau tulisan "(no output)" pas dry run daripada simulasi yang tujuannya buat rasa aman malah crash dan bikin ragu.

Sejak fix satu baris itu, saya jadi rutin jalanin --check dulu sebelum ngedor fleet 9router. Cepet, nggak deg-degan, dan kalo crash lagi saya tau pasti bukan karena report-nya.

Sumber

  1. Ansible docs: Validating tasks, check mode and diff mode
  2. Ansible docs: ansible.builtin.default filter
  3. Ansible docs: Conditionals

Artikel terkait