ansibleでmoduleを作ってみる

引き続きansibleです。今回はmoduleを作ってみます。

ansibleのmoduleはplaybookではカバーしきれないものをmoduleにする、という 感じで切り分けるといいそうです。

また、標準で使えるモジュールは ここ にあります。たくさんありますね。 ansibleの人たちはPythonのように"battery included"を目指しているとのこと です。でも、名前空間を導入してもうちょっと整理したりメタなモジュールを 用意したほうがいいんじゃないかなぁとか思ったりします。

モジュールの概要

モジュールは標準出力を出すものであればどんな言語で書かれようと構いませ ん。標準のモジュールはshell scriptとpythonですが、rubyだろうとperlだろ うと構いません。

標準出力はJSONです。JSONライブラリがないような言語のために空白区切りで =でつないだkey-valueペアでも大丈夫とのことですが、JSONの方がいいでしょ う。

自作のモジュールは環境変数 ANSIBLE_LIBRARY で指定するか --module-path を指定することで読み込めます。

入力

モジュールへの引数は、key=value の形式の列としてファイルに格納されま す。ファイルの場所は第一引数に渡されます。

後ほど出てくるテストモジュールで

./test-module -m ./time.py -a "f=2006 d=220"

としたら、

f=2006 d=220

という内容のファイルが作られます。これを読むわけですね。

pythonのコードを一部分だけ抜き出すとこんな感じになります。

args_file = sys.argv[1]  # 一番目の引数をfileパスに
args_data = file(args_file).read()  # 中身を読み込む

arguments = shlex.split(args_data)  # 空白で区切る
for arg in arguments:
    if arg.find("=") != -1:  # =が入ってないなら飛ばす
        (key, value) = arg.split("=")  # =で区切ってkey, valueを取り出す
        if key == "time":
            pass

標準出力

上で述べたようにJSON形式が期待されます。

テスト方法

% ansible/hacking/test-module -m ./somethingnew.py

とかやるとテストできます。

作ってみる

では実際に作ってみましょう。JSON形式ということで、使う言語は、そう、やっ ぱりgo言語ですね。

ansibleのモジュールは実行時にターゲットホストに転送され、そこで実行され ます(未確認だけどそういうことを書いているblogがあった)。go言語は static-linkされたバイナリを作成しますので、ターゲットホストの環境に全く 依存しないモジュールが作れます。その分ファイルサイズはでかくなりますけ どね。

というわけで、 Ansible module dev にある、現在時刻を返すモジュー ルを作成してみました。

package main

import (
    "fmt"
    "os"
    "io/ioutil"
    "strings"
    "time"
    "encoding/json"
)

type Time struct {
    Time string `json:"time"`  // 通常json.Marshalすると"Time"と大文字になってしまうので。
}

func main() {
    f := "2006-01-02 15:04:06"  // defaultのフォーマット

    data, err:= ioutil.ReadFile(os.Args[1])  // 読み込み
    if err != nil {panic(err)}
    arg := strings.Split(string(data), " ")[0] // 空白区切りで最初だけ読む

    if strings.Contains(arg, "="){  // = があるということは、引数指定あり
       f = strings.Split(arg, "=")[1]  // keyの部分をチェックしていないのはご愛嬌
    }

    t := time.Now()  // 現在時刻取得
    m := Time{t.Format(f)}  // Time型に入れる
    s, err := json.Marshal(m)  // jsonに
    if err != nil {
        fmt.Println(err)
    }else{
        fmt.Printf("%s", s)
    }
}

テストしてみます。

% ./test-module -m ./main -a "f=2006"  # 引数付き
***********************************
RAW OUTPUT
{"time":"2013"}

%./test-module -m ./main  # 引数なしの場合
***********************************
RAW OUTPUT
{"time":"2013-04-22 23:37:13"}

では、実行してみましょう。

% ansible -vvv all -m time -M . --ask-pass
SSH password:
<127.0.0.1> ESTABLISH CONNECTION FOR USER: sample on PORT 22 TO 127.0.0.1
<127.0.0.1> EXEC /bin/sh -c 'mkdir -p
$HOME/.ansible/tmp/ansible-1366642710.56-42277018789729 && chmod a+rx
$HOME/.ansible/tmp/ansible-1366642710.56-42277018789729 && echo $HOME/.ansible/tmp/ansible-1366642710.56-42277018789729'
<127.0.0.1> REMOTE_MODULE time.go
127.0.0.1 | FAILED => failure encoding into utf-8

あ、あれ…?

コードを読んでみると、どうもテキストしか想定していないような雰囲気…

ということで、

現時点ではgo言語でモジュールは作成できませんでしたが、python他スクリプ ト言語では作成できます。

また、この件はMLに報告し、「おれは必要ないと思うんだけど、まあ実装して やるよー」と返事があったので、そのうち実行できるようになるかと思います。

そもそもansibleは「ターゲット環境に可能な限り依存しないし汚さない」とい う考え方で作られているので、サーバーが顧客の持ち物だったりしてRubyや Chefを入れられない、という人には案外使い道があるかもしれません。

そんな感じで。

Comments

comments powered by Disqus