Ansible file Module: Create Delete Modify File Permissions

Ansible file module ใช้จัดการ filesystem objects บน remote server ครอบคลุมการสร้างและลบไฟล์/directory, กำหนด permissions, ownership และ symbolic links โดยไม่ต้องใช้ command รัน chmod, chown หรือ mkdir ตรง ๆ ซึ่งมักไม่ idempotent

บทความนี้ครอบคลุม state ทั้งหมดที่รองรับ, การกำหนด owner, group และ mode, การสร้าง directory tree, symlink management, การใช้ร่วมกับ loop และ pattern สำหรับ prepare filesystem ก่อน deploy application

file Module พื้นฐาน — State ที่รองรับ

file module ควบคุมสถานะของ filesystem object ผ่าน parameter state ซึ่งรองรับหลายค่าแต่ละค่ามีพฤติกรรมต่างกัน

---
- name: file module state examples
  hosts: all
  become: true
  tasks:
    # touch: สร้างไฟล์ว่างถ้าไม่มี, อัพเดต timestamp ถ้ามีอยู่แล้ว
    - name: Create empty file
      file:
        path: /var/log/myapp/app.log
        state: touch
        owner: appuser
        group: appgroup
        mode: '0644'

    # directory: สร้าง directory (ถ้ามีอยู่แล้วจะไม่ทำอะไร)
    - name: Create directory
      file:
        path: /opt/myapp/data
        state: directory
        owner: appuser
        group: appgroup
        mode: '0755'

    # absent: ลบไฟล์หรือ directory (idempotent)
    - name: Remove file or directory
      file:
        path: /tmp/old-config.bak
        state: absent

    # link: สร้าง symbolic link
    - name: Create symbolic link
      file:
        src: /opt/myapp/current
        dest: /usr/local/bin/myapp
        state: link

    # hard: สร้าง hard link
    - name: Create hard link
      file:
        src: /etc/ssl/certs/myapp.crt
        dest: /opt/myapp/ssl/myapp.crt
        state: hard

Owner, Group และ Mode

กำหนด ownership และ permissions ได้ทั้งตอนสร้างและตอนอัพเดต object ที่มีอยู่แล้ว module จะ idempotent — ถ้า permissions ตรงแล้วจะไม่เปลี่ยนอะไร

---
- name: Permissions and ownership management
  hosts: all
  become: true
  tasks:
    # กำหนด owner, group, mode พร้อมกัน
    - name: Set file permissions
      file:
        path: /etc/myapp/app.conf
        owner: root
        group: appgroup
        mode: '0640'     # owner=rw, group=r, others=none
        state: file      # state: file ตรวจว่าเป็นไฟล์และแก้ไข attributes

    # ใช้ symbolic mode (u+x, g-w, o=r)
    - name: Make script executable
      file:
        path: /opt/myapp/bin/start.sh
        mode: 'u+x,g+x'
        state: file

    # กำหนดเฉพาะ mode (ไม่เปลี่ยน owner)
    - name: Restrict config file
      file:
        path: /etc/myapp/secret.conf
        mode: '0600'     # owner only
        state: file

    # setuid/setgid bit
    - name: Set SUID bit on binary
      file:
        path: /usr/local/bin/myapp
        mode: '4755'     # SUID + rwxr-xr-x
        state: file

    # สร้าง directory พร้อมกำหนด permissions
    - name: Create log directory
      file:
        path: /var/log/myapp
        state: directory
        owner: appuser
        group: adm
        mode: '0750'

สร้าง Directory Tree ลึกหลายระดับ

สำหรับ path ที่ลึกหลายระดับ ใช้ recurse: true เพื่อกำหนด permissions กับทุก object ใน tree หรือใช้ loop สร้างหลาย directory พร้อมกัน

---
- name: Directory tree management
  hosts: all
  become: true
  tasks:
    # สร้าง directory path ลึกหลายชั้น (mkdir -p equivalent)
    - name: Create deep directory path
      file:
        path: /opt/myapp/data/uploads/images
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    # recurse: กำหนด permissions กับทุกไฟล์/directory ใน tree
    - name: Fix permissions recursively
      file:
        path: /opt/myapp
        state: directory
        owner: appuser
        group: appgroup
        recurse: true

    # สร้างหลาย directory พร้อมกันด้วย loop
    - name: Create application directories
      file:
        path: "{{ item }}"
        state: directory
        owner: appuser
        group: appgroup
        mode: '0755'
      loop:
        - /opt/myapp/bin
        - /opt/myapp/conf
        - /opt/myapp/data
        - /opt/myapp/logs
        - /opt/myapp/tmp

Symbolic Links

Symlink ใน Ansible ทำงาน idempotent — ถ้า link ชี้ไปที่ถูกต้องแล้วจะไม่เปลี่ยนอะไร ใช้ force: true เพื่อแทนที่ link ที่มีอยู่แล้ว

---
- name: Symlink management
  hosts: all
  become: true
  tasks:
    # สร้าง version symlink สำหรับ blue-green deploy
    - name: Create current version symlink
      file:
        src: /opt/myapp/releases/v2.5.0
        dest: /opt/myapp/current
        state: link
        force: true    # แทนที่ link เดิมถ้ามีอยู่แล้ว

    # symlink สำหรับ config
    - name: Link config to expected path
      file:
        src: /etc/myapp/nginx-site.conf
        dest: /etc/nginx/sites-enabled/myapp.conf
        state: link

    # ลบ symlink
    - name: Remove symlink
      file:
        path: /etc/nginx/sites-enabled/old-site.conf
        state: absent

    # ตรวจสอบว่า symlink ถูก resolve ไปที่ถูกต้อง
    - name: Check symlink target
      stat:
        path: /opt/myapp/current
      register: symlink_info

    - name: Show symlink target
      debug:
        msg: "current → {{ symlink_info.stat.lnk_target }}"
      when: symlink_info.stat.islnk

ลบไฟล์และ Directory

state: absent ลบ object ไม่ว่าจะเป็นไฟล์, directory (รวม subdirectory ทั้งหมด), หรือ symlink — idempotent ถ้าไม่มีอยู่แล้วจะไม่ error

---
- name: Cleanup examples
  hosts: all
  become: true
  tasks:
    # ลบไฟล์
    - name: Remove config
      file:
        path: /etc/myapp/old.conf
        state: absent

    # ลบ directory และ contents ทั้งหมด (rm -rf equivalent)
    - name: Remove old release directory
      file:
        path: /opt/myapp/releases/v1.0.0
        state: absent

    # ลบหลาย path พร้อมกันด้วย loop
    - name: Clean up temporary files
      file:
        path: "{{ item }}"
        state: absent
      loop:
        - /tmp/deploy.lock
        - /tmp/myapp-install.tmp
        - /var/run/myapp.pid

    # ลบ symlink (ไม่ลบ target)
    - name: Remove nginx site symlink
      file:
        path: /etc/nginx/sites-enabled/disabled-site.conf
        state: absent

Pattern: Prepare Filesystem ก่อน Deploy

ตัวอย่าง Playbook เตรียม directory structure, permissions และ symlinks สำหรับ application ก่อน deploy จริง

---
- name: Prepare filesystem for application
  hosts: appservers
  become: true
  vars:
    app_name: myapp
    app_version: "2.5.0"
    app_user: appuser
    app_group: appgroup

  tasks:
    - name: Create application directory structure
      file:
        path: "{{ item.path }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: "{{ item.mode }}"
      loop:
        - { path: "/opt/{{ app_name }}/releases", mode: "0755" }
        - { path: "/opt/{{ app_name }}/releases/v{{ app_version }}", mode: "0755" }
        - { path: "/opt/{{ app_name }}/shared/config", mode: "0750" }
        - { path: "/opt/{{ app_name }}/shared/logs", mode: "0755" }
        - { path: "/opt/{{ app_name }}/shared/tmp", mode: "0755" }

    - name: Set config directory permissions (restricted)
      file:
        path: "/opt/{{ app_name }}/shared/config"
        owner: root
        group: "{{ app_group }}"
        mode: '0750'
        state: directory

    - name: Create log with correct ownership
      file:
        path: "/opt/{{ app_name }}/shared/logs/app.log"
        state: touch
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0644'

    - name: Switch current symlink to new release
      file:
        src: "/opt/{{ app_name }}/releases/v{{ app_version }}"
        dest: "/opt/{{ app_name }}/current"
        state: link
        force: true

    - name: Remove old releases
      file:
        path: "/opt/{{ app_name }}/releases/{{ item }}"
        state: absent
      loop: "{{ old_releases | default([]) }}"

สรุป

file module เป็นเครื่องมือหลักสำหรับจัดการ filesystem บน Ansible รองรับ state ครบทั้ง touch, directory, absent, link, hard และ file การกำหนด owner, group, mode ทำได้ทั้งตอนสร้างและตอนแก้ไข object ที่มีอยู่แล้ว

Pattern ที่ควรจำ: ใช้ loop สร้างหลาย directory พร้อมกันแทนการเขียน task ซ้ำ, ใช้ recurse: true เฉพาะเมื่อต้องการแก้ permissions ทั้ง tree (ระวัง performance บน directory ใหญ่), ใช้ force: true กับ symlink เพื่อให้ replace link เดิมได้ และใช้ state: absent แทน command: rm เสมอ