背景と注意事項
Ansible
にはrpm
モジュールがない。
そうれはそう、yum
があるから。
構成管理ツールなので--force
とかできるrpm
はAnsible
の中では非推奨というわけだ。
が、現場では過去のシステムを継ぎ接ぎで開発しているものがあって、どうしても64bitマシンに32bitのrpm
を入れたいときがあるわけだ。これをyum
で実装した際に「64bitのパッケージあるから入れてやらないぞ」となったりするわけ。
あるべき姿は構成管理を視野にいれた開発であって、これは非推奨モジュールである。
実現したいこと
あんまり難しいのやるつもりはなくて
-
rpm
ファイルは既にcopy
などでノードに配置されている- これはいつか
copy
相当の組み込みでファイルを配置できるようにしたい
- これはいつか
-
rpm
コマンドのオプションを受け取ってそのまま使う- これもいつかはフラグ管理したい
- 指定された
rpm
ファイルが存在することを確認 - 指定された
rpm
が未インストールであることを確認 - 指定された
rpm
をまとめて、指定されたオプションでインストール
こんぐらいだろうか。
雛形を作る
過去の記事を見てね。
#!/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で渡した配列をモジュールの中でループに使用するには
:#(省略)
# 引数受け取り
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
コマンド使っちゃったけど、流石にそんぐらいモジュール何かあるだろうと考えた。
:#(省略)
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
を配置していないぞ。
- 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"
]
}
あれ・・・ちょっとデバッグ。
# 冗長になるので変数入れ直し
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:
だと動いた。うーん、なぜなのか。
:#(省略)
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
上記の標準出力で得られたものがパッケージ名だ。
この処理をモジュールに組み込む
:#(省略)
# 冗長になるので変数入れ直し
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インストール要否判定
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)
:#(省略)
試運転
- 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"
]
}
いいんじゃないでしょうか。
作成したモジュール
#!/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()