Ansibleモジュールのサンプルコードを実行してみた

 ansibleで動作するモジュールを自作する方法を調べてみました。参考にしたのは、docs.ansible.com の [Developing Modules](http://docs.ansible.com/developing_modules.html) です。掲載されているpythonのサンプルコードを実際に動かして、モジュールの挙動を確認してみました。実行環境は CentOS 7 + ansible 1.9.1 です。

# モジュール配置先パスの設定

 モジュールの配置先のパスを /etc/ansible/ansible.cfg に記述します。
 "library = 配置先ディレクトリ" の行を追加します。

 [記述例]

    [defaults]
    inventory               = /etc/ansible/hosts
    library                 = /root/ansible/modules/    # 追加

# サンプルコードの作成

 [Developing Modules](http://docs.ansible.com/developing_modules.html) に掲載されているサンプルコード(time.py)を ansible.cfg で記載したモジュール配置先に作成します。

    $ vi /root/ansible/modules/time.py

 サンプルコードの内容は以下の通りです。datetimeモジュールを呼び出して、現在時刻の設定や取得を行うことができます。必要なクラスのインポート、モジュールに渡されるパラメタの解析方法、処理結果の出力方法など、基本的な手続きがコメント付きで記述されています。

    #!/usr/bin/python
    
    # import some python modules that we'll use.  These are all
    # available in Python's core
    
    import datetime
    import sys
    import son
    import os
    import shlex
    
    # read the argument string from the arguments file
    args_file = sys.argv[1]
    args_data = file(args_file).read()
    
    # for this module, we're going to do key=value style arguments
    # this is up to each module to decide what it wants, but all
    # core modules besides 'command' and 'shell' take key=value
    # so this is highly recommended
    
    arguments = shlex.split(args_data)
    for arg in arguments:
    
        # ignore any arguments without an equals in it
        if "=" in arg:
    
            (key, value) = arg.split("=")
    
            # if setting the time, the key 'time'
            # will contain the value we want to set the time to

            if key == "time":

                # now we'll affect the change.  Many modules
                # will strive to be 'idempotent', meaning they
                # will only make changes when the desired state
                # expressed to the module does not match
                # the current state.  Look at 'service'
                # or 'yum' in the main git tree for an example
                # of how that might look.
    
                rc = os.system("date -s \"%s\"" % value)

                # always handle all possible errors
                #
                # when returning a failure, include 'failed'
                # in the return data, and explain the failure
                # in 'msg'.  Both of these conventions are
                # required however additional keys and values
                # can be added.

                if rc != 0:
                    print json.dumps({
                        "failed" : True,
                        "msg"    : "failed setting the time"
                    })
                    sys.exit(1)
    
                # when things do not fail, we do not
                # have any restrictions on what kinds of
                # data are returned, but it's always a
                # good idea to include whether or not
                # a change was made, as that will allow
                # notifiers to be used in playbooks.
    
                date = str(datetime.datetime.now())
                print json.dumps({
                    "time" : date,
                    "changed" : True
                })
                sys.exit(0)

    # if no parameters are sent, the module may or
    # may not error out, this one will just
    # return the time

    date = str(datetime.datetime.now())
    print json.dumps({
        "time" : date
    })

# ansibleコマンドによるサンプルモジュールの実行

 モジュールは処理対象のサーバに転送されてから実行され、その結果がjson形式で返されます。

・現在時刻の取得

 パラメタを指定せずにモジュールを実行すると、現在時刻が json形式で出力されます。

[実行例]

    $ ansible localhost -m time -k
    SSH password: 
    localhost | success >> {
        "time": "2015-05-23 21:50:50.921787"
    }

・現在の設定

 パラメタとして、-a オプションで時刻を指定して実行すると、モジュール内で "date -s" コマンドを実行してから、datetimeモジュールで変更後の時刻を取得し、その結果が json形式で出力されます。

[実行例]


    $ ansible localhost -m time -k -a "time=21:50:50"
    SSH password: 
    localhost | success >> {
        "changed": true, 
        "time": "2015-05-23 21:50:50.006286"
    }

　この場合、実行結果に "changed": true と表示されますが、これは時刻変更が行われたことを示すためのものです。

・エラー発生時の動作

 パラメタとして、-a オプションで不正な値(時刻ではない文字列)して実行すると、モジュール内での "date -s" コマンドの実行が失敗し、実行結果として以下のようなエラー情報が出力されます。

[実行例]


    # ansible localhost -m time -k -a "time=hoge"
    SSH password: 
    localhost | FAILED >> {
        "failed": true, 
        "msg": "failed setting the time"
    }

　この場合、実行結果に "failed": true と表示されますが、これは処理が失敗したことを示すためのものです。また、"msg": には処理が失敗した理由を出力します。

# playbook によるサンプルモジュールの実行

 サンプルモジュールをplaybookから実行する場合は、playbookを以下のように記述します。

[記述例]


    ---
    - hosts: "{{ target }}"
      tasks:
      - name: set time
        time: time="{{ time }}"

　tasks: セクションに実行するモジュール名 "time" と モジュールに渡すパラメータ "time="{{ time }}" を記述します。 

 playbookを実行する際には、-e オプションでモジュールを実行するノード(target)と設定する時刻(time)を指定します。

[実行例]


    $ ansible-playbook time.yml -k -e "target=localhost time=23:27:00"
    SSH password: 
    
    PLAY [localhost] ************************************************************** 
    
    GATHERING FACTS *************************************************************** 
    ok: [localhost]
    
    TASK: [set time] ************************************************************** 
    changed: [localhost]
    
    PLAY RECAP ******************************************************************** 
    localhost                  : ok=2    changed=1    unreachable=0    failed=0   


 playbookの実行結果でモジュールの実行結果のサマリが出力されます。

# おまけ

 サンプルモジュールのテストが済んだら、NTPで時刻を正しい値に戻しておく事を強くお勧めします:sweat_smile: 

    $ ntpdate time.asia.apple.com