ろーてくxyz blog

ローテクを駆使してIT関係でサヴァイヴしようとする人による備忘録

Ansible copy module (Advent Calendar 2018)

copy module (backup時のタイムスタンプの話)

はじめに

本記事は Ansible Blogger 2018 (sponsored by Red Hat) Advent Calendar 2018 の14日目の記事です。 qiita.com

早速本日分を書かせていただきます。

みなさん backup使ってますか?

好きなモジュールについてのお題では御座いますが、 本日はcopy moduleを含めいくつかのmoduleに実装されている backupパラメータについてをお話させて頂ければと思います。

今回のお話の環境

・Centos7
・Python3.6 + Pipenv
・Ansible Version

# ansible --version
ansible 2.7.4

とりあえずbackup=yesでcopyをしてみる

まずはplaybook

playbookといってもcopyモジュールを使ってパラメータとして backup=yesを引き渡すシンプルなものです。
今回は昔お試しで使ってたplaybookがsnmpd.confの差し替えだったのでそのまま使いました。
内容については大きな意味はありません。
しかも検証環境でlocalでrootアクセスです。
実際にご利用の際は環境にあわせて適宜設定ください。

---
- hosts: all
  user: root
  tasks:
    - name: Replace file
      template :
        src=replace_template
        dest=/etc/snmp/snmpd.conf
        mode=0600
        owner=root
        group=root
        backup=yes
      tags:
        - replace_file

フォルダの構成はこんな感じで差し替えファイルはtemplatesに入れておきました

# ls -F
Pipfile  ansible.cfg  hosts  playbook.yml  templates/

いざ実行

# ansible-playbook playbook.yml

PLAY [all] *****************************************************************

TASK [Gathering Facts] ***************************************************
ok: [127.0.0.1]

TASK [Replace file] ********************************************************
changed: [127.0.0.1]

PLAY RECAP **************************************************************
127.0.0.1                  : ok=2    changed=1    unreachable=0    failed=0

#

changed=1になりました。 完了したようです。 どれどれ ターゲット先を見てみますか。

# ls -ltr /etc/snmp/snmpd.conf*
-rw------- 1 root root 18861 10月 31 08:52 /etc/snmp/snmpd.conf.2699.2018-11-10@13:01:39~
-rw------- 1 root root     0 11月 10 13:01 /etc/snmp/snmpd.conf

ちゃんとバックアップされファイルが差し変わっており、
想定とおりの動作が確認できました!
(今回はplaybookのsrcに指定したファイルは空ファイルです。。)

しかし、ちょっと待った!
バックアップのタイムスタンプフォーマットが好みではない
ではなく、今まで手作業でやってた頃のフォーマットにしてみたいなぁなど

自分に見やすい形のフォーマットに変更したいっ!!!!!

そう、本日のメインはここです。

backupパラメータのコードを改変する

Ansibleのソースを探す

Ansibleのソースをpipで落とします

# pip download ansible --no-deps
Collecting ansible
  Using cached https://files.pythonhosted.org/packages/9e/df/b7ce359f9cc16864e0d5c9c93efe69f576fc437e74549bcc142f02cd4216/ansible-2.7.4.tar.gz
  Saved ./ansible-2.7.4.tar.gz
Successfully downloaded ansible
#

展開してソースがあるディレクトリへ

# tar zxvf ansible-2.7.4.tar.gz
# cd ansible-2.7.4

該当のPythonコードをみつける(たぶんこれ)

# ls -l lib/ansible/module_utils/basic.py
-rw-r--r-- 1 root root 117169 12月  1  2018 lib/ansible/module_utils/basic.py

grepするとこんな感じ

# fgrep backup lib/ansible/module_utils/basic.py
    backup=dict(),  # Used by a few modules to create a remote backup before updating the file
    def backup_local(self, fn):
        '''make a date-marked backup of the specified file, return True or False on success or failure'''
        backupdest = ''
            # backups named basename.PID.YYYY-MM-DD@HH:MM:SS~
            backupdest = '%s.%s.%s' % (fn, os.getpid(), ext)
                self.preserved_copy(fn, backupdest)
                self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, to_native(e)))
        return backupdest

コードを修正してみる

以下な感じにしてみました。
今回はわかりやすように時間表記に含まれている記号とpidを消し去りました。

# diff -up lib/ansible/module_utils/basic.py{.org,}
--- lib/ansible/module_utils/basic.py.org       2018-12-01 04:11:03.000000000 +0900
+++ lib/ansible/module_utils/basic.py   2018-11-10 13:05:07.151889137 +0900
@@ -2437,8 +2437,8 @@ class AnsibleModule(object):
         backupdest = ''
         if os.path.exists(fn):
             # backups named basename.PID.YYYY-MM-DD@HH:MM:SS~
-            ext = time.strftime("%Y-%m-%d@%H:%M:%S~", time.localtime(time.time()))
-            backupdest = '%s.%s.%s' % (fn, os.getpid(), ext)
+            ext = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time()))
+            backupdest = '%s.%s' % (fn, ext)

             try:
                 self.preserved_copy(fn, backupdest)

インストール

tar.gzで固め直してpipインストールをします。
長々と表示されていますが依存関係のあるパッケージで、 一度インストールされているのでこのような感じで表示されます。
また、ansible-rebuild の部分はtarで固める前にsetup.pyを 変更しています。(前のバージョンを消すのをお忘れなく)
(サンプルではpipでのインストールで説明します)

固めます

# tar zcvf ansible-2.7.4.tar.gz ansible-2.7.4

インストール!!!

# pip install ./ansible-2.7.4.tar.gz
Processing ./ansible-2.7.4.tar.gz
Requirement already satisfied: jinja2 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from ansible-rebuild==2.7.4) (2.10)
Requirement already satisfied: PyYAML in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from ansible-rebuild==2.7.4) (3.13)
Requirement already satisfied: paramiko in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from ansible-rebuild==2.7.4) (2.4.2)
Requirement already satisfied: cryptography in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from ansible-rebuild==2.7.4) (2.4.2)
Requirement already satisfied: setuptools in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from ansible-rebuild==2.7.4) (40.6.2)
Requirement already satisfied: MarkupSafe>=0.23 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from jinja2->ansible-rebuild==2.7.4) (1.1.0)
Requirement already satisfied: pynacl>=1.0.1 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from paramiko->ansible-rebuild==2.7.4) (1.3.0)
Requirement already satisfied: bcrypt>=3.1.3 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from paramiko->ansible-rebuild==2.7.4) (3.1.4)
Requirement already satisfied: pyasn1>=0.1.7 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from paramiko->ansible-rebuild==2.7.4) (0.4.4)
Requirement already satisfied: six>=1.4.1 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from cryptography->ansible-rebuild==2.7.4) (1.12.0)
Requirement already satisfied: idna>=2.1 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from cryptography->ansible-rebuild==2.7.4) (2.8)
Requirement already satisfied: cffi!=1.11.3,>=1.7 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from cryptography->ansible-rebuild==2.7.4) (1.11.5)
Requirement already satisfied: asn1crypto>=0.21.0 in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from cryptography->ansible-rebuild==2.7.4) (0.24.0)
Requirement already satisfied: pycparser in /root/.local/share/virtualenvs/ANSIBLE-mn6ZODMw/lib/python3.6/site-packages (from cffi!=1.11.3,>=1.7->cryptography->ansible-rebuild==2.7.4) (2.19)
Building wheels for collected packages: ansible-rebuild
  Running setup.py bdist_wheel for ansible-rebuild ... done
  Stored in directory: /root/.cache/pip/wheels/e5/1a/f7/1d3a8103d7ad08bbfe992efcf6d67e2e3eaad12774818f5cb8
Successfully built ansible-rebuild
Installing collected packages: ansible-rebuild
Successfully installed ansible-rebuild-2.7.4

インストール状況を確認

# pip list
Package         Version
--------------- -------
ansible-rebuild 2.7.4
asn1crypto      0.24.0
bcrypt          3.1.4
cffi            1.11.5
cryptography    2.4.2
idna            2.8
Jinja2          2.10
MarkupSafe      1.1.0
paramiko        2.4.2
pip             18.1
pyasn1          0.4.4
pycparser       2.19
PyNaCl          1.3.0
PyYAML          3.13
setuptools      40.6.2
six             1.12.0
wheel           0.32.3

実行テスト

実行されました

# ansible-playbook playbook.yml
SSH password:

PLAY [all] ********************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************
ok: [127.0.0.1]

TASK [Replace file] ***********************************************************************************************************************
changed: [127.0.0.1]

PLAY RECAP ********************************************************************************************************************************
127.0.0.1                  : ok=2    changed=1    unreachable=0    failed=0

#

さぁバックアップファイル名フォーマットの確認

変更した指定のフォーマットになりました!!

# ls -ltr /etc/snmp/snmpd.conf*
-rw------- 1 root root 18861 10月 31 08:52 /etc/snmp/snmpd.conf.20181110-131439
-rw------- 1 root root     0 11月 10 13:14 /etc/snmp/snmpd.conf

いかがでしたでしょうか。
拘っても仕方ない部分ですが@~がついたフォーマット形式は今まで使ってこなかった事もあり、手で運用していた時代のポリシーに合わせる事を目的にこの様な修正をやっていました。
恐らく簡単に修正できるものなので既にやられている方も多いのではないかと思いますが、何かの参考にでもなりましたら。
(数年前の話ですが実際に私の昔の同僚も全く同じ事を思っていて同じ事してました・・・)

注意

・全てのmoduleを使用してでの動作確認はしていません
・いきなり本番環境ではつかわない
・冪等性が崩れる可能性がありますのでナンセンスとも思う