LoginSignup
1
3

More than 3 years have passed since last update.

Ansible 自作モジュール rpm操作

Posted at

背景と注意事項

Ansibleにはrpmモジュールがない。

そうれはそう、yumがあるから。

構成管理ツールなので--forceとかできるrpmAnsibleの中では非推奨というわけだ。

が、現場では過去のシステムを継ぎ接ぎで開発しているものがあって、どうしても64bitマシンに32bitのrpmを入れたいときがあるわけだ。これをyumで実装した際に「64bitのパッケージあるから入れてやらないぞ」となったりするわけ。

あるべき姿は構成管理を視野にいれた開発であって、これは非推奨モジュールである。

実現したいこと

あんまり難しいのやるつもりはなくて

  • rpmファイルは既にcopyなどでノードに配置されている
    • これはいつかcopy相当の組み込みでファイルを配置できるようにしたい
  • rpmコマンドのオプションを受け取ってそのまま使う
    • これもいつかはフラグ管理したい
  • 指定されたrpmファイルが存在することを確認
  • 指定されたrpmが未インストールであることを確認
  • 指定されたrpmをまとめて、指定されたオプションでインストール

こんぐらいだろうか。

雛形を作る

過去の記事を見てね。

rpm.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from ansible.module_utils.basic import AnsibleModule

# メイン処理
#-----------------------------------------------------------
def main():
    # AnsibleModuleクラス: moduleを作成
    module = AnsibleModule(

        # 引数受け取り
        argument_spec=dict(

            # ココに引数受け取り処理を書く

        ),
        # 引数チェックを有効
        supports_check_mode=True
    )

    # 指定されたファイルの存在チェック

    # 変更要否判定
    changed = False

    # rpmインストール

    # 終了処理

if __name__ == '__main__':
    main()

こんなところかな

引数の受け取り

今回受け付ける引数は以下のとおり

引数名 必須 デフォルト値 引数の意味
pkgs - パッケージのリスト
opts -Uvh rpmコマンドに与えるオプション
chdir / パッケージ格納ディレクトリ

ちなみにリストの受け取り方が調べて悩んでわからず、某Q&Aに投稿したけど翌日自己解決した。
参考:Ansible with_listで渡した配列をモジュールの中でループに使用するには

rpm.py
:#(省略)

# 引数受け取り
argument_spec=dict(
    # パッケージリスト(必須,list型)
    pkgs = dict(required = True, type = list),
    # オプション(str型,[-Uvh])
    opts = dict(required = False, type = str, default = '-Uvh'),
    # パッケージ格納ディレクトリ(str型)
    chdir = dict(required = False, type = str, default = '/'),
),
# 引数チェックを有効
supports_check_mode=True

:#(省略)

なお、上記のように部分的に抜粋する場合は行頭のインデントを削っています。横に長くなっちゃうから。

指定されたファイルの存在チェック

前回、mkfifoするモジュールを作成したときは何も考えずにstatコマンドやtestコマンド使っちゃったけど、流石にそんぐらいモジュール何かあるだろうと考えた。

参考:pythonでファイルの存在を確認する

rpm.py
:#(省略)

from ansible.module_utils.basic import AnsibleModule
import os

:#(省略)

# 冗長になるので変数入れ直し
pkgs = module.params['pkgs']
chdir = module.params['chdir']

# パッケージリスト毎にループ
for pkg in pkgs:
    # 指定されたファイルの存在チェック
    if (os.path.isfile(chdir + '/' + pkg)):
        # ファイルが配置されていない
        module.fail_json(msg = chdir + '/' + pkg + ' is not found.')

:#(省略)

心配なのでココで一度動作確認しておこう。まだ/tmp/rpmを配置していないぞ。

test.yml
- name: ntp install
  rpm:
    pkgs: '{{ item }}'
    opts: '-ivh'
    chdir: '/tmp'
  with_list:
    - [ 'ntp-4.2.6p5-15.el6.centos.x86_64.rpm','ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm' ]
動作確認
$ ansible-playbook -i test_grp -l test_srv -u root test.yml -vvv
:#(省略)

ok: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ]
}

あれ・・・ちょっとデバッグ。

rpm.py
# 冗長になるので変数入れ直し
pkgs = module.params['pkgs']
chdir = module.params['chdir']
num = 0
hog = 0
# パッケージリスト毎にループ
for pkg in pkgs:
    num += 1
    # 指定されたファイルの存在チェック
    if (os.path.isfile(chdir + '/' + pkg)):
        hog += 1
        # ファイルが配置されていない
        module.fail_json(msg = chdir + '/' + pkg + ' is not found.')

:#(省略)

module.exit_json(num = num, hog = hog)
実行結果
ok: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "hog": 0,  ★
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ],
    "num": 2  ★
}

うーんif (os.path.isfile(chdir + '/' + pkg)):が間違えてるのかな。いろいろ試した結果if os.path.isfile(chdir + '/' + pkg) == False:だと動いた。うーん、なぜなのか。

rpm.py
:#(省略)

from ansible.module_utils.basic import AnsibleModule
import os

:#(省略)

# 冗長になるので変数入れ直し
pkgs = module.params['pkgs']
chdir = module.params['chdir']

# パッケージリスト毎にループ
for pkg in pkgs:
    # 指定されたファイルの存在チェック
    if os.path.isfile(chdir + '/' + pkg) == False:
        # ファイルが配置されていない
        module.fail_json(msg = chdir + '/' + pkg + ' is not found.')

:#(省略)

変更要否判定

変更要否、つまり既にすべてのpkgがインストールされていたら作業は不要なわけだ。rpmパッケージがインストールされているかどうかは以下のコマンドでわかる。

$ rpm -q ntp-4.2.6p5-15.el6.centos.x86_64
ntp-4.2.6p5-15.el6.centos.x86_64
$ echo $?
0

$ rpm -q hoge
パッケージ hoge はインストールされていません。
$ echo $?
1

しかしrpmファイルは、ファイル名の末尾.rpmを抜いたものがパッケージ名とは限らない。

$ ls -1 jdk-8u192-linux-x64.rpm
jdk-8u192-linux-x64.rpm

$ rpm -q jdk-8u192-linux-x64
パッケージ jdk-8u192-linux-x64 はインストールされていません。

$ rpm -qa | grep jdk
jdk1.8-1.8.0_192-fcs.x86_64

上記の例だとファイル名はjdk-8u192-linux-x64.rpmだがパッケージ名はjdk1.8-1.8.0_192-fcs.x86_64である。

これを判別するためにはrpm -qp rpmファイル名でパッケージ名を調べる必要がある。

$ rpm -qp jdk-8u192-linux-x64.rpm
警告: jdk-8u192-linux-x64.rpm: ヘッダ V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
jdk1.8-1.8.0_192-fcs.x86_64

上記の標準出力で得られたものがパッケージ名だ。

この処理をモジュールに組み込む

rpm.py
:#(省略)

# 冗長になるので変数入れ直し
pkgs = module.params['pkgs']
chdir = module.params['chdir']

# 変更要否判定
changed = False

# インストール対象rpmファイルリスト
rpms = []

# パッケージリスト毎にループ
for pkg in pkgs:

    # ファイルのフルパスを作成
    fpath = chdir + '/' + pkg

    # 指定されたファイルの存在チェック
    if os.path.exists(fpath) == False:
        # ファイルが配置されていない
        module.fail_json(msg = fpath + ' is not found.')

    # rpmファイルからパッケージ名を取得
    rc, pkg_name, stderr = module.run_command('rpm -qp ' + fpath)

    # パッケージのインストール状況確認
    rc, stdout, stderr = module.run_command('rpm -q ' + pkg_name)

    # インストール済みか
    if rc != 0:
        # インストールされていないので変更要
        changed = True
        # インストール対象に追加
        rpms.append(fpath)

:#(省略)

rpmインストール

インストール対象rpmリストrpmsが空っぽだったら終了・・・という判定は chagned != True に任せることにする。

rpm.py
:#(省略)

# rpmインストール要否判定
if changed != True:
    # インストール対象なし
    module.exit_json()

# rpmリスト作成
rpm_line = ' '.join(rpms)

# rpmインストール
rc, stdout, stderr = module.run_command('rpm ' + module.params['opts'] + ' ' + rpm_line)

# rpmインストール結果判定
if rc != 0:
    # インストール失敗
    module.fail_json(stdout = stdout, stderr = stderr)

# 終了処理
module.exit_json(changed = changed)

:#(省略)

試運転

test.yml
- name: ntp install
  rpm:
    pkgs: '{{ item }}'
    opts: '-ivh'
    chdir: '/tmp'
  with_list:
    - [ 'ntp-4.2.6p5-15.el6.centos.x86_64.rpm','ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm' ]
# パッケージファイルが配置されていることを確認
$ ssh root@192.168.56.104 "ls -l /tmp/*rpm"
-rw-rw-r--. 1 root root 614364  5月 11 17:01 2020 /tmp/ntp-4.2.6p5-15.el6.centos.x86_64.rpm
-rw-rw-r--. 1 root root  80836  5月 11 17:00 2020 /tmp/ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm

# パッケージファイルがインストールされていないことを確認
$ ssh root@192.168.56.104 "rpm -qa | grep ntp"
$

# 実行!
$ ansible-playbook -i test_grp -l test_srv -u root test.yml -vvv
:#(省略)
changed: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": true,
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ],
    "msg": "/tmp/ntp-4.2.6p5-15.el6.centos.x86_64.rpm /tmp/ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
}

# 冪等性確認のためもう一度実行!
$ ansible-playbook -i test_grp -l test_srv -u root test.yml -vvv
ok: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ]
}

いいんじゃないでしょうか。

作成したモジュール

rpm.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from ansible.module_utils.basic import AnsibleModule
import os

# メイン処理
#-----------------------------------------------------------
def main():
    # AnsibleModuleクラス: moduleを作成
    module = AnsibleModule(

        # 引数受け取り
        argument_spec=dict(
            # パッケージリスト(必須,list型)
            pkgs = dict(required = True, type = list),
            # オプション(str型,[-Uvh])
            opts = dict(required = False, type = str, default = '-Uvh'),
            # パッケージ格納ディレクトリ(str型)
            chdir = dict(required = False, type = str, default = '/'),
        ),
        # 引数チェックを有効
        supports_check_mode=True
    )

    # 冗長になるので変数入れ直し
    pkgs = module.params['pkgs']
    chdir = module.params['chdir']

    # 変更要否判定
    changed = False

    # インストール対象rpmファイルリスト
    rpms = []

    # パッケージリスト毎にループ
    for pkg in pkgs:

        # ファイルのフルパスを作成
        fpath = chdir + '/' + pkg

        # 指定されたファイルの存在チェック
        if os.path.exists(fpath) == False:
            # ファイルが配置されていない
            module.fail_json(msg = fpath + ' is not found.')

        # rpmファイルからパッケージ名を取得
        rc, pkg_name, stderr = module.run_command('rpm -qp ' + fpath)

        # パッケージのインストール状況確認
        rc, stdout, stderr = module.run_command('rpm -q ' + pkg_name)

        # インストール済みか
        if rc != 0:
            # インストールされていないので変更要
            changed = True
            # インストール対象に追加
            rpms.append(fpath)

    # rpmインストール要否判定
    if changed != True:
        # インストール対象なし
        module.exit_json()

    # rpmリスト作成
    rpm_line = ' '.join(rpms)

    # rpmインストール
    rc, stdout, stderr = module.run_command('rpm ' + module.params['opts'] + ' ' + rpm_line)

    # rpmインストール結果判定
    if rc != 0:
        # インストール失敗
        module.fail_json(stdout = stdout, stderr = stderr)

    # 終了処理
    module.exit_json(changed = changed)

if __name__ == '__main__':
    main()
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3