Ansible Filters: ประมวลผล Variables ด้วย Jinja2 Filters

Jinja2 Filters คือฟังก์ชันที่ใช้แปลงหรือประมวลผล variable ใน Playbook โดยใส่ | ตามหลังตัวแปร เช่น {{ username | upper }} ทำให้ไม่ต้องเขียน task เพิ่มเพื่อแปลงค่า และทำให้ template อ่านง่ายขึ้นมาก

บทความนี้รวบรวม filters ที่ใช้บ่อยที่สุดในทางปฏิบัติ ครอบคลุมตั้งแต่การแปลง string, จัดการ list, ตรวจสอบค่าว่าง, ไปจนถึงการ format JSON และ YAML สำหรับใช้ใน config files

String Filters: แปลง Text

Filters กลุ่ม string ใช้แปลงรูปแบบข้อความก่อนนำไปใส่ใน task หรือ template

---
- name: String filter examples
  hosts: localhost
  vars:
    username: "  John Doe  "
    hostname: "web-server-01"
  tasks:
    - name: Show string transformations
      debug:
        msg:
          - "{{ username | trim }}"           # "John Doe"  (ตัด whitespace)
          - "{{ username | trim | upper }}"    # "JOHN DOE"
          - "{{ username | trim | lower }}"    # "john doe"
          - "{{ hostname | replace('-', '_') }}"  # "web_server_01"
          - "{{ hostname | split('-') }}"      # ['web', 'server', '01']
          - "{{ hostname | length }}"          # 13
          - "{{ 'nginx' | capitalize }}"       # "Nginx"

Default Filter: ป้องกัน Undefined Variable

Filter default คือหนึ่งใน filters ที่สำคัญที่สุด ป้องกัน error เมื่อ variable ไม่มีค่า

---
- name: Default filter examples
  hosts: all
  tasks:
    - name: Use default when variable undefined
      debug:
        msg: "Port: {{ app_port | default(8080) }}"
      # ถ้า app_port ไม่ได้กำหนดไว้ จะใช้ 8080 แทน

    - name: Default with empty string check
      debug:
        msg: "Env: {{ deploy_env | default('production', true) }}"
      # true = ใช้ default แม้ variable มีค่าเป็น '' หรือ false

    - name: Conditional with default
      debug:
        msg: "Log level: {{ log_level | default('info') | upper }}"

พารามิเตอร์ที่สองของ default(value, boolean): ถ้าเป็น true จะ fallback ไปใช้ค่า default แม้ตัวแปรมีค่าเป็น empty string หรือ false ด้วย

List Filters: จัดการ Array

Filters กลุ่ม list ใช้บ่อยมากเมื่อทำงานกับ inventory หรือ variable ที่เป็น array

---
- name: List filter examples
  hosts: localhost
  vars:
    servers: ["web01", "web02", "db01", "db02"]
    numbers: [3, 1, 4, 1, 5, 9, 2, 6]
  tasks:
    - name: List operations
      debug:
        msg:
          - "{{ servers | length }}"           # 4
          - "{{ servers | first }}"            # "web01"
          - "{{ servers | last }}"             # "db02"
          - "{{ numbers | sort }}"             # [1, 1, 2, 3, 4, 5, 6, 9]
          - "{{ numbers | unique }}"           # [3, 1, 4, 5, 9, 2, 6]
          - "{{ numbers | min }}"              # 1
          - "{{ numbers | max }}"              # 9
          - "{{ numbers | sum }}"              # 31
          - "{{ servers | join(', ') }}"       # "web01, web02, db01, db02"
          - "{{ servers | select('match', 'web.*') | list }}"  # web servers only

selectattr และ map: กรองและแปลง List of Dicts

เมื่อ variable เป็น list ของ dictionary selectattr และ map เป็นคู่ที่ใช้บ่อยที่สุด

---
- name: selectattr and map examples
  hosts: localhost
  vars:
    users:
      - { name: alice, role: admin, active: true }
      - { name: bob,   role: dev,   active: true }
      - { name: carol, role: dev,   active: false }

  tasks:
    - name: Get active users only
      debug:
        msg: "{{ users | selectattr('active') | list }}"

    - name: Get admin users
      debug:
        msg: "{{ users | selectattr('role', 'equalto', 'admin') | list }}"

    - name: Get just names of active users
      debug:
        msg: "{{ users | selectattr('active') | map(attribute='name') | list }}"
      # ['alice', 'bob']

    - name: Create comma-separated list of dev usernames
      debug:
        msg: "{{ users | selectattr('role', 'equalto', 'dev') | map(attribute='name') | join(', ') }}"
      # "bob, carol"

Math และ Type Conversion Filters

Filters สำหรับคำนวณและแปลง type ใช้บ่อยเมื่อทำงานกับค่าตัวเลขจาก facts หรือ variables

---
- name: Math and type conversion
  hosts: all
  tasks:
    - name: Calculate values
      debug:
        msg:
          - "{{ ansible_memtotal_mb | int }}"              # แปลงเป็น integer
          - "{{ '3.14' | float }}"                         # แปลงเป็น float
          - "{{ ansible_memtotal_mb * 0.8 | int }}"        # 80% ของ RAM
          - "{{ (100 / 3) | round(2) }}"                   # 33.33
          - "{{ [1,2,3] | sum }}"                          # 6
          - "{{ 'true' | bool }}"                          # True (Python bool)

    - name: String to number and arithmetic
      set_fact:
        worker_count: "{{ [ansible_processor_vcpus * 2, 8] | min }}"
        # worker count = min(CPU*2, 8) เพื่อไม่ให้เกิน 8

JSON และ YAML Filters

Filters เหล่านี้มีประโยชน์มากเมื่อต้องสร้าง config files หรือ debug structure ที่ซับซ้อน

---
- name: JSON and YAML filters
  hosts: localhost
  vars:
    config:
      host: db01
      port: 5432
      ssl: true
  tasks:
    - name: Convert dict to JSON string
      debug:
        msg: "{{ config | to_json }}"
      # {"host": "db01", "port": 5432, "ssl": true}

    - name: Pretty-print JSON for config files
      debug:
        msg: "{{ config | to_nice_json }}"

    - name: Convert to YAML format
      debug:
        msg: "{{ config | to_nice_yaml }}"

    - name: Parse JSON string back to dict
      set_fact:
        parsed: "{{ '{\"key\": \"value\"}' | from_json }}"

Path และ File Filters

Filters กลุ่มนี้ใช้จัดการ path ของไฟล์และไดเรกทอรี เหมาะสำหรับงาน deploy และ config management

---
- name: Path filter examples
  hosts: localhost
  vars:
    full_path: "/var/www/myapp/config/app.conf"
  tasks:
    - name: Path operations
      debug:
        msg:
          - "{{ full_path | basename }}"      # "app.conf"
          - "{{ full_path | dirname }}"       # "/var/www/myapp/config"
          - "{{ full_path | splitext }}"      # ['/var/www/myapp/config/app', '.conf']
          - "{{ full_path | expanduser }}"    # แปลง ~ เป็น home dir
          - "{{ '/var/www' | realpath }}"     # absolute canonical path

ต่อ Filters หลายตัว (Chaining)

สามารถต่อหลาย filters เข้าด้วยกันได้โดยเรียงด้วย | ค่าจะไหลจากซ้ายไปขวาทีละ filter

---
- name: Chained filter examples
  hosts: all
  vars:
    raw_servers: ["WEB-01 ", " DB-02", " cache-03 "]
  tasks:
    - name: Clean and normalize server list
      set_fact:
        clean_servers: >-
          {{ raw_servers
             | map('trim')
             | map('lower')
             | list }}
      # ['web-01', 'db-02', 'cache-03']

    - name: Build nginx upstream from server list
      debug:
        msg: "server {{ item }}:80;"
      loop: "{{ clean_servers | select('match', 'web.*') | list }}"

สรุป

Jinja2 Filters ช่วยให้ Playbook จัดการข้อมูลได้โดยไม่ต้องเพิ่ม task หรือ script แยก Pattern ที่ควรจำ: ใส่ | default(value) เสมอสำหรับ variable ที่อาจไม่มีค่า, ใช้ selectattr + map คู่กันเมื่อต้องกรอง list of dicts, และใช้ | int หรือ | float ก่อนคำนวณเพื่อป้องกัน type error

Filters สามารถต่อกันได้ไม่จำกัด ยิ่งเรียนรู้ filters มากขึ้นเท่าไร โค้ดใน template และ Playbook จะกระชับและอ่านง่ายขึ้นเท่านั้น