てくなべ (tekunabe)

ansible / network / automation

[Ansible] lineinfile モジュールの基本的な使い方(テキストファイルの行単位の編集)

■ はじめに

Ansible には、テキストファイルを行単位で編集する lineinfile モジュール があります。httpd.conf などの設定ファイルの設定項目を書き換えるといった用途に利用できます。

この記事では、 lineinfile モジュールの公式ドキュメントに記載されている使用例をベースにして、使い方を説明します。

なお、公式ドキュメントの使用例は、Playbook 単位ではなくtask 単位で記載されています。この記事では Playbook 単位で例示します。

動作確認環境

  • Ansible 2.3.0, 2.7.8
  • CentOS 7.6 (Ansible 側、管理対象ホスト側とも)

目次


■ 単純に行置換する

Playbook

/etc/selinux/config 内の '^SELINUX=' にマッチする行を SELINUX=enforcing に置換する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - lineinfile:
        path: /etc/selinux/config
        regexp: '^SELINUX='
        line: 'SELINUX=enforcing'
  • path オプション
    • 対象のファイル名のパス名を指定します。(必須)
  • regexp オプション
    • 編集対象とする行を示す正規表現を指定します。
    • マッチする行が複数ある場合、最後にマッチした行が対象になります。
    • マッチしなかった場合は EOF が対象(デフォルトの backrefs: no の場合 )
  • line オプション
    • 挿入/置換する行を指定します。
    • state オプションが present (デフォルト) の場合のみ有効です。

本タスクで指定していないその他のオプション例

  • ownergroup オプション
    • 所有者情報を指定できます。
  • mode オプション
  • create オプション
    • path で指定したファイルが存在しなかった場合にファイル作成するかどうか(デフォルト no
  • backup オプション
    • 編集前のファイルをバックアップとして別途保存するかどうか(デフォルト no

実行ログ

ここでは、SELINUX=disabled の状態で Playbook を実行します。

$ cat /etc/selinux/config | grep "^SELINUX=" 
SELINUX=disabled
$ ansible-playbook -i inventory lineinfile_simple.yml 

PLAY [linux] ***************************************************************

TASK [lineinfile] **********************************************************
changed: [linux1]

PLAY RECAP *****************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   

$ cat /etc/selinux/config | grep "^SELINUX="  
SELINUX=enforcing

無事に SELINUX=enforcing に置換されました。

なお、この後もう一度同じ Playbook を実行した場合は、すでに SELINUX=enforcing になっていることを検出して、とくに何もしません。そのため、対象ファイルの更新日時も変わりません。


■ 行を削除する

Playbook

/etc/sudoers 内の '^%wheel' にマッチする行を削除する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - lineinfile:
        path: /etc/sudoers
        state: absent
        regexp: '^%wheel'
  • state オプション
    • マッチした行を削除したい場合は state: absent を指定します。

実行ログ

ここでは、'^%wheel' にマッチする行がある状態で Playbook を実行します。

$ sudo cat /etc/sudoers | grep "^%wheel"
%wheel  ALL=(ALL)       ALL
$ ansible-playbook -i inventory lineinfile_absent.yml 

PLAY [linux] ***************************************************************

TASK [lineinfile] **********************************************************
changed: [linux1]

PLAY RECAP *****************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   

$ sudo cat /etc/sudoers | grep "^%wheel"
$ # 表示なし

無事に '^%wheel' にマッチする行が削除されました。


■ マッチしなかった場合に挿入したい箇所を指定して挿入する

Playbook

./test.txt 内に 、'^Listen ' にマッチする行があれば line の内容に置換し、マッチする行がなければ insertafter で指定する '^#Listen ' にマッチする行の次の行に Listen 8080 を挿入する Playbook です。

設定ファイル内にコメントアウトされた設定があるときに、その次の行に有効な設定値を挿入したい場合などに利用できます。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - lineinfile:
        path: ./test.txt
        regexp: '^Listen '
        insertafter: '^#Listen '
        line: 'Listen 8080'
  • insertafter オプション
    • regexp オプションで指定した正規表現にマッチする行がなかった場合、このオプションで指定した正規表現にマッチする行の「次の行」に line オプションの行を挿入します。
    • insertafter: EOF を指定した場合は、ファイルの最後の行に挿入します。
    • 似たオプションとして infertbefore オプションがあり、マッチする行の「前の行」に挿入します。

本タスクで指定していないその他のオプション例

  • firstmatch オプション(Ansible 2.5から利用可能)
    • insertafterinsertbefore オプションの正規表現で複数の行がマッチした場合、最初にマッチした行を基準とするかどうかを指定します。(デフォルト no

実行ログ

ここでは、#Listen 80 がある状態で Playbook を実行します。

$ cat ./test.txt 
abc
abc
#Listen 80
abc
abc
$ ansible-playbook -i inventory lineinfile_after.yml 

PLAY [linux] *******************************************************************

TASK [lineinfile] **************************************************************
changed: [linux1]

PLAY RECAP *********************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   

$ cat ./test.txt
abc
abc
#Listen 80
Listen 8080
abc
abc

無事に '^#Listen ' にマッチする次の行に Listen 8080 が挿入されました。

なお、もし ./test.txt

abc
abc
Listen 80
abc
abc

のように regexp オプションで指定した '^Listen ' にマッチする行がある場合は、単純に置換されて以下のようになります。

abc
abc
Listen 8080
abc
abc


■ 正しく編集が行われるかを事前に検証する

Playbook

/etc/sudoers の内容を編集して保存する前に、/usr/sbin/visudo -cf コマンドでファイルのフォーマットを確認する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - lineinfile:
        path: /etc/sudoers
        state: present
        regexp: '^%ADMIN ALL='
        line: '%ADMIN ALL=(ALL) NOPASSWD: ALL'
        validate: '/usr/sbin/visudo -cf %s'
  • validate オプション
    • 編集後のファイルの内容が正しいか検証するためのコマンドを指定します。対象を %s で指定します。

実行ログ

ここでは、SELINUX=disabled の状態で Playbook を実行します。

$ sudo cat /etc/sudoers | grep '^%ADMIN ALL='
$ 
$ ansible-playbook -i inventory lineinfile_validate.yml     
PLAY [linux] ****************************************************************

TASK [lineinfile] ***********************************************************
changed: [linux1]

PLAY RECAP ******************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
$ sudo cat /etc/sudoers | grep '^%ADMIN ALL='
%ADMIN ALL=(ALL) NOPASSWD: ALL

無事に /etc/sudoers の内容が編集されました。

なお、もし line: '%ADMIN ALL=(ALL) XXXXXXXx: ALL' のように無効なフォーマットを指定して Playbook を実行した場合は、以下のようなエラーメッセージが表示されて異常終了します。

TASK [lineinfile] *************************************************************************************
fatal: [linux1]: FAILED! => {"changed": false, "msg": "failed to validate: rc:1 error:>>> /tmp/tmpWQcMFk: syntax error near line 121 <<<\n"}

■ 応用編: チェックモードであらかじめ編集前後の diff を確認する

ansible-playbook コマンドの --check--diff オプションを一緒に利用すると、実際はファイルの内容を編集せずに、あらかじめ編集前後の diff を確認できます。

以下の Playbook で実行例を確認します。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - lineinfile:
        path: ./test.txt
        regexp: '^Listen '
        insertafter: '^#Listen '
        line: 'Listen 8080'
$ cat test.txt      # 対象ファイルの事前確認
abc
abc
#Listen 80
abc
abc

$ ansible-playbook -i inventory lineinfile_after.yml --diff --check 

PLAY [linux] **********************************************************

TASK [lineinfile] ******************************************************
--- before: ./test.txt (content)
+++ after: ./test.txt (content)
@@ -1,5 +1,6 @@
 abc
 abc
 #Listen 80
+Listen 8080
 abc
 abc

changed: [linux1]

PLAY RECAP *********************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   

$ cat test.txt      # ファイルの内容は変更されていない
abc
abc
#Listen 80
abc
abc


■ まとめ

公式ドキュメントの使用例をベースにして、lineinfile モジュール の使い方を説明しました。

他に「こんなことできるかな?」と気になる事がありましたら、公式ドキュメントで詳細をご確認ください。

docs.ansible.com

また、lineinfile モジュールは Files modules に分類されています。Files modules には、他にも、指定した正規表現にマッチするすべての文字列を置換する replace や、行ではなくブロック単位で編集する blockinfile などのモジュールがあります。詳細は Files modules の一覧からご確認ください。