LoginSignup
11
12

More than 5 years have passed since last update.

Ansible v2でPythonじゃない自作モジュールを使う際の注意点

Last updated at Posted at 2015-08-31

:warning: これはアルファ時点の古い情報です。Ansible 2.0.0正式版では、旧スタイルのモジュールも動く様に対応されています。
ただし、これから作る自作モジュールのスタイルとしてはnew, non_native_want_jsonが推奨される点は変わりませんので、記事は残しておきます。

Ansible 2.0.0のalpha版が出たので、そろそろ本腰入れて使ってみようと思ったら、bashスクリプトで書いた自作モジュールが動かなくなっていたので、対応方法のメモ。

自作モジュール: parrot

ここでは、以下の様な与えられた引数をオウム返しするだけのparrotと言うカスタム・モジュールを例にして話を進めます。
(ちなみに、このまま使うと値にスペースが入る場合には正しく動かないです)

parrot
#!/bin/bash

# 引数ファイルが与えられなかったらエラー
if [ -z $1 ]; then
    echo "{\"failed\": true, \"msg\": \"no arguments provided.\"}"
    exit 1
fi

result="{\"changed\": false"
# 引数をkey, valueに分解してresultに突っ込む
for kv in $(cat $1); do
    echo $kv
    escaped=$(echo $kv | sed -e 's/"/\\\"/g')
    key=${escaped%%=*}
    value=${escaped#*=}
    if [ $key ]; then
        result+=", \"$key\": \"$value\""
    fi
done
result+="}"

echo $result
exit 0

現象

1.9.2では動く

このparrotモジュールをAnsible 1.9.2で実行すると、下の様に入力した引数がそのまま実行結果として出力されます。

$ ansible localhost -m parrot -a 'foo=bar hoge=fuga'

localhost | success >> {
    "changed": false,
    "foo": "bar",
    "hoge": "fuga"
}

2.0.0だと動かない

ところが、Ansible 2.0.0で全く同じ様に実行しても、引数が与えられていないと怒られてしまいした!

ansible localhost -m parrot -a 'hoge=fuga foo=bar'

localhost | FAILED! => {
    "changed": false,
    "failed": true,
    "msg": "no arguments provided."
}

エラーの原因

これは一体どうしたことだろうと、-vvvを付けて、実行されているコマンドを確認してみると…

# 1.9.2の場合
<localhost> EXEC ['/bin/sh', '-c', u'LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /bin/bash /tmp/ansible-tmp-1441005841.0-58446508593101/parrot /tmp/ansible-tmp-1441005841.0-58446508593101/arguments; rm -rf /tmp/ansible-tmp-1441005841.0-58446508593101/ >/dev/null 2>&1']

# 2.0.0の場合
localhost EXEC LANG=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /bin/bash /tmp/ansible-tmp-1441005817.19-202560708428255/parrot; rm -rf "/tmp/ansible-tmp-1441005817.19-202560708428255/" >/dev/null 2>&1

上の実行結果をよく見てみると、1.9.2ではparrotへの第一引数としてargumentsと言うファイルが渡されているのに対して、2.0.0ではparrotが引数なしで実行されているのがわかります。
parrot内では第一引数で渡されたパスのファイルを読みに行ってansible引数を取得しているので、argumentsファイルが存在しないと当然エラーになってしまいます。

ファイル経由の引数渡しはv2では使えない!

では、なぜv2ではargumentsファイルが渡されていないのでしょうか?
実はAnsible v1のモジュールには3つの異なるスタイルが存在し、それぞれ異なる形式で引数情報が渡されています。

Ansibleモジュールの3つのスタイル

  • old
    • 1.4未満で使われていた形式
    • モジュールへの引数はスペース区切りのargumentsファイルで渡される
      • e.g. hoge=fuga foo=bar
    • スペース入りの値などの扱いが困難
    • parrotモジュールはこの形式
  • non_native_want_json
    • oldと同様に引数はargumentsファイル経由で渡される
    • oldと違って、argumentsファイルの中身はJSON
      • e.g. {"foo": "bar", "hoge": "fuga"}
    • モジュール内にWANT_JSONと書かれている場合にこのスタイルになる。
  • new
    • 1.4以降の組み込みモジュールで使われている形式
    • 変数系の情報は下で説明する仕様でモジュールファイル内に展開される
    • 引数用の外部ファイルは使われない。
    • モジュール内に#<<INCLUDE_ANSIBLE_MODULE_COMMON>>と書かれている場合にこのスタイルになる。

ソースを確認したところ、v2ではargumentsファイル経由での引数渡しが使えなくなっているようです。
具体的な後方互換ポリシーについては未確認ですが、一度削除されたファイル経由での引数渡しが今後v2で再実装される見込みは薄いでしょう。

v1でのモジュール内文字列展開の仕様

  • "<<ANSIBLE_VERSION>>": モジュールを実行するAnsibleのバージョン文字列
  • "<<INCLUDE_ANSIBLE_MODULE_ARGS>>": モジュールに与えられた名前なし引数文字列
  • "<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>": モジュールに与えられた名前付き引数のpythonコード用JSON文字列(reprを使っている)
  • <<SELINUX_SPECIAL_FILESYSTEMS>>: SELinux情報

v2でのモジュール内文字列展開の仕様変更点

なお、v2では展開の仕様にもいくつか変更点がありました。

  • 追加された展開パターン
    • <<INCLUDE_ANSIBLE_MODULE_WINDOWS_ARGS>>
      • PowerShell用の名前付き引数のJSON文字列
      • "<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>"との違いは、引用符が含まれないだけ
      • "<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>"はpythonコード用だが、こちらは単純にダンプされたJSON
  • 削除された展開パターン
    • "<<INCLUDE_ANSIBLE_MODULE_ARGS>>"
      • そもそもv2ではcommand系などの一部の組み込みモジュール以外では、名前なし引数の利用が許可されていません
    • <<SELINUX_SPECIAL_FILESYSTEMS>>

自作モジュールを新方式に対応させる

ということで、parrotをv2でも使えるようにするには1ファイル内で引数を受け取れるようにモジュールを修正するしかなさそうです。
モジュール内の文字列展開はバージョン1.4以降であればv1でも使えますので、v1/v2間での互換性についてはそんなに気にする必要はなさそうです。
嘘でした。
"<<INCLUDE_ANSIBLE_MODULE_COMPLEX_ARGS>>"はpython内でそのまま使えるようにreprを使っているので、{"foo": "bar\"baz"}の様なJSONの場合、'{"foo": "bar\\"baz"}'の様にエスケープされてしまいます。
そのため、v2用には<<INCLUDE_ANSIBLE_MODULE_WINDOWS_ARGS>>を使い、v1用にnon_native_want_jsonを使う、と言う対応が必要になりそうです。
Windows用じゃないモジュールに<<INCLUDE_ANSIBLE_MODULE_WINDOWS_ARGS>>を入れるというのは気持ちが悪いけれど…
2015/09/11時点のdevelブランチ最新では代わりに<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>という表記も可能になっています。

修正されたparrot

という訳で、修正後のparrotはこんな感じになりました。

fixed_parrot
#!/bin/bash
# WANT_JSON

ANSIBLE_VERSION="<<ANSIBLE_VERSION>>"

# jqがなかったらエラー
if ! type jq >/dev/null 2>&1; then
    echo "{\"failed\": true, \"msg\": \"jq not found.\"}"
    exit 1
fi

# v1の場合は$1に引数ファイルのパスが入る
if [[ $ANSIBLE_VERSION == 1.* ]]; then
    MODULE_COMPLEX_ARGS=$(cat $1)
# v2の場合はINCLUDE_ANSIBLE_MODULE_JSON_ARGSを使う
else
    MODULE_COMPLEX_ARGS=$(cat << 'EOF'
<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>
EOF
                       )
fi

echo $MODULE_COMPLEX_ARGS | jq '.changed=false'
exit 0

JSONの操作については、jqを使っちゃってます。
自分の場合、Ansibleモジュールをわざわざbashで書きたいのって、CoreOSの操作くらいなんですが、CoreOSには幸いjqがインストールされているので楽しました。

ポイントとしては

  • WANT_JSONコメントでnon_native_want_jsonスタイルを適用
  • ANSIBLE_VERSIONの値によって、引数の取得方法を場合分け

と言ったあたりでしょうか。

まとめ

今回はbashスクリプトを例にとって自作モジュールのv2対応法を試してみましたが、このやり方は組み込みでヘルパーが用意されているPythonやPowerShell以外でモジュールを作る場合にはそのまま適用可能なものです。
v2への移行は時期尚早と言う場合でも、WANT_JSONコメントを使って引数をJSONとして取得する方法は、JSONパーサーが使える言語/環境であれば是非とも導入すべきかと思います。

参考

https://gist.github.com/mildred/9ce28b926b567548037d (モジュール・スタイルごとの挙動の違いについて)

追記(2015/09/11)

<<INCLUDE_ANSIBLE_MODULE_WINDOWS_ARGS>>がイケていなかったので、<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>と言うリプレイサーを足したら、取り込んでくれました。
https://github.com/ansible/ansible/pull/12267

11
12
2

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
11
12