このエントリーをはてなブックマークに追加

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を入れられない、という人には案外使い道があるかもしれません。

そんな感じで。