#author("2020-03-07T21:48:07+09:00","default:haikikyou","haikikyou")
#author("2020-03-08T01:30:03+09:00","default:haikikyou","haikikyou")
[[Ansible]]
#contents
* Ansible Moduleの開発 [#nc7313c0]

Ansibleでは独自のモジュールを組み込み実行することができる。~
pythonでプログラムが書ければ、以下のようにtaskの中で作成したモジュールを使用することができる。他にもfilterやlookup pluginsなど、module以外にも独自の拡張ができる。

#geshi{{{
- my_module:
    param_a: hello
    paramb_b: world
}}}

module作成は、公式のドキュメントやansibleのmodulesディレクトリ下のデフォルトで用意されているmoduleを見ると良いだろう。

&label(warn){参考};
- https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
- https://github.com/ansible/ansible/tree/devel/lib/ansible/modules

''moduleの名前は、他のモジュール名と重複しないように注意する。''
* moduleの作成 [#v1f1f382]

まずはシンプルなmoduleを見てみる。~
以下は、helloというモジュール名でechoで受け取ったメッセージを返すだけのモジュールである。

#geshi(python){{{
# Python2と3で同様の振る舞いをするようなおまじない
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

# https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#ansible-metadata-block

# Ansibleが使用するメタデータ
ANSIBLE_METADATA = {
    # moduleのバージョンではない!metadata schemaのバージョン
    'metadata_version': '1.1', 
    # 以下のステータスはAnsible Core Teamのメンバによって判断される
    'status': ['preview'],
    'supported_by': 'community'
}

# ドキュメント生成に使用される
# 以下でドキュメントを表示できる
#   $ ansible-doc -M /path/to/moduledir-t module hello
DOCUMENTATION = r'''
---
module: hello
short_description: my hello
requirements:
description:
  - echo message
options:
seealso:
author:
  - moritetu
'''

EXAMPLES = r'''
- hello:
    echo: hello world
'''

RETURN = r'''
message:
  description: echo message
  returned: always
  type: str
  sample: hello world
'''
 
# AnsibleModuleの読み込み
from ansible.module_utils.basic import AnsibleModule

def main():

    module = AnsibleModule(
        # moduleのパラメータ
        argument_spec=dict(
            echo=dict(type='str', required=True),
        ),
        supports_check_mode=True
    )

    if module.check_mode:
        module.exit_json(**result)

    result = dict(
        changed=False,
        message=module.params['echo'],
    )

    # moduleの出力
    module.exit_json(**result)

if __name__ == '__main__':
    main()
}}}

このmoduleは以下のような使用方法となる。

#geshi{{{
- hello:
    echo: hello world
}}}

ドキュメントを記述していれば、&code(){ansible-doc};で出力できる。

#geshi{{{
$ ansible-doc -M library -t module hello
> HELLO    (/test/ansible/module_test/playbooks_test/library/hello.py)

        echo message

  * This module is maintained by The Ansible Community
AUTHOR: moritetu
        METADATA:
          status:
          - preview
          supported_by: community


EXAMPLES:

- hello:
    echo: hello world


RETURN VALUES:

message:
  description: echo message
  returned: always
  type: str
  sample: hello world
}}}




** moduleのパラメータタイプ [#d0df9c85]
-str
-list
-dict
-bool
-int
-float
-path
#geshi{{{
param_path: $HOME
}}}
-raw~
そのままの値
-jsonarg
-json
#geshi{{{
param_json: {"key":"value"}
}}}
-bytes
--サイズをhuman readableな指定で書く
-- 例:1Mb、2KB、8Kbyte
#geshi{{{
param_bytes: 1MB #=> 1048576
}}}
-bits
--サイズをhuman readableな指定で書く
-- 例:1Mb、2Kb、1024bit
#geshi{{{
param_bytes: 1Mb #=> 1048576
}}}

''hello.py''

#geshi(python){{{
import json
from ansible.module_utils.basic import AnsibleModule


def main():
    module = AnsibleModule(
        argument_spec=dict(
            _path=dict(type='path', required=False),
            _raw=dict(type='raw', required=False),
            _jsonarg=dict(type='jsonarg', required=False),
            _json=dict(type='json', required=False),
            _bytes=dict(type='bytes', required=False),
            _bits=dict(type='bits', required=False),
        ),
        supports_check_mode=True
    )

    module.exit_json(
        _path=module.params['_path'],
        _raw=module.params['_raw'],
        _jsonarg=json.loads(module.params['_jsonarg']),
        _json=json.loads(module.params['_json']),
        _bytes=module.params['_bytes'],
        _bits=module.params['_bits'],
    )


if __name__ == '__main__':
    main()
}}}

''param.yaml''

#geshi(yaml){{{
- hosts: localhost
  connection: local
  gather_facts: no
  tasks:
    - hello:
        _path: ${HOME}
        _raw: raw
        _jsonarg: {"hoge":[1,2,3]}
        _json: {"hoge":[1,2,3]}
        _bytes: 8Kbyte
        _bits: 16bit
      register: facts

    - debug: msg={{ facts }}
}}}

実行結果は以下のとおり。

#geshi{{{
# ansible-playbook params.yaml

PLAY [localhost] ****************************************************************************

TASK [hello] ********************************************************************************
ok: [localhost]

TASK [debug] ********************************************************************************
ok: [localhost] => {
    "msg": {
        "_bits": 16,
        "_bytes": 8192,
        "_json": {
            "hoge": [
                1,
                2,
                3
            ]
        },
        "_jsonarg": {
            "hoge": [
                1,
                2,
                3
            ]
        },
        "_path": "/home/guest",
        "_raw": "raw",
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "failed": false
    }
}

PLAY RECAP **********************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

}}}

&label(sample){サンプル}; &ref(./param_test.tar.gz,100%);

&label(warn){参考};
- https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec
** Argument spec [#od91ab75]

*** required [#ce66734f]

パラメータが必須か否かを指定する。

*** type [#bca67333]

moduleのパラメータで列挙した値を指定する。

*** default [#n120287a]

パラメータに指定がない場合のデフォルト値。デフォルトは&code(){None};。

*** fallback [#ra0ecfb5]

パラメータが渡されないときにフォールバックされる。~
例えば、以下は&code(){username};が指定されないとき、環境変数の&code(){ANSIBLE_NET_USERNAME};から値を探索する(&code(){os.environ};)。

#geshi(python){{{
username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']))
}}}

*** choices [#i7d8f6d8]

選択式のパラメータを提供する。

#geshi(python){{{
# str
mode=dict(required=True, type='str', choices=['init', 'update'])
# list
mode=dict(required=True, type='list', choices=['init', 'update'])
}}}

#geshi{{{
# 使用例

# str
- mymod:
    mode: init

# list
- mymod:
    mode: ['init']
}}}

*** no_log [#iafb3054]

secret情報などに使用し、出力がマスクされる。

#geshi(python){{{
secret=dict(required=True, type='str', no_log=True)
}}}

#geshi{{{
# VALUE_SPECIFIED_IN_NO_LOG_PARAMETERという文字列でマスクされる
- mymod:
    secret: password
}}}

*** elements [#x9e1a46e]

&code(){type=list};と組み合わせて使用する。~
パラメータで指定されたリストの要素を&code(){elements};で指定されたタイプとして解釈する。

#geshi(python){{{
elm=dict(required=True, type='list', elements='str')
}}}


#geshi{{{
# strとして解釈され変換される
- mymod
    elm: [1,2,3]
}}}


*** aliases [#ea49435d]

パラメータに別名を指定できる。

*** options [#kd777a4e]

サブパラメータ(階層的な)を持たせたい場合に使用する。

*** apply_defaults [#vf210ac4]

&code(){options};と一緒に使用する。サブパラメータのデフォルト値を指定する。

*** removed_in_version [#l0bbdfb3]

deprecatedであることを示す。

#geshi{{{
elm=dict(required=True, type='list', elements='str',aliases=['pkg'], removed_in_version=2.9),
}}}

#geshi{{{
[DEPRECATION WARNING]: Param 'elm' is deprecated. See the module docs for more information. This feature will be removed
 in version 2.9. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
}}}
** moduleの終了 [#f0ffd957]

module実行の終了は、以下のメソッドを呼び出す。

*** exit_json [#db36dc95]

- exit 0で終了
- 出力に&code(){changed};がない場合は、&code(){changed = False};が結果に追加される

*** fail_json [#m170fad8]

- &code(){failed};が&code(){True};にセットされる。
- &code(){msg};の指定が必須である。
- exit 1で終了

* moduleのロード [#i0799552]

Ansibleがデフォルトでmoduleを探索するパスに設置する、または、ansible.cfgでmoduleのパスを指定しすればよい。そうすればAnsibleがプレイブックを実行時にロードしてくれる。

- 環境変数&code(){ANSIBLE_LIBRARY};
- &code(){~/.ansible/plugins/modules/};
- &code(){/usr/share/ansible/plugins/modules/};

** ansible.cfgに指定 [#a2ac2850]

&code(){ansible.cfg};の&code(){library};というパラメタに自作したmoduleのパスを指定する。~
ディレクトリ構成は以下のとおり。~

#geshi{{{
$ tree
.
├── ansible.cfg
├── mod
│   └── hello.py
└── test.yaml

1 directory, 3 files
}}}

''ansible.cfg''

以下では、&code(){mod};というディレクトリを相対パスでしている。
#geshi(ini){{{
[defaults]
library = ./mod
}}}

以下では、&code(){ansible.cfg};があるディレクトリでplaybookを実行する。

#geshi{{{
$ ansible-playbook test.yaml -v
Using /test/ansible/module_test/ansible_cfg_module_test/ansible.cfg as config file

PLAY [localhost] **********************************************************************************

TASK [hello] **************************************************************************************
ok: [localhost] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "message": "world"}

PLAY RECAP ****************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

}}}

&label(sample){サンプル}; &ref(./ansible_cfg_module_test.tar.gz,100%);
** libraryのローカルロード [#c15ad126]

playbook及び特定のroleに対しmoduleをロードさせることができる。~
playbookや特定のroleの下にlibraryフォルダを設定することで可能である。~
実際に自作moduleがロードされるか確認する。

テストで使用するソースは以下のとおり。

''hello.py''

#geshi(python){{{
from ansible.module_utils.basic import AnsibleModule

def main():
    module = AnsibleModule(
        argument_spec=dict(
            echo=dict(type='str', required=True),
        ),
        supports_check_mode=True
    )

    if module.check_mode:
        module.exit_json(**result)

    result = dict(
        changed=False,
        message=module.params['echo'],
    )

    module.exit_json(**result)


if __name__ == '__main__':
    main()
}}}


*** &label(study){実験};  プレイブックに適用する [#b0469210]

playbookのあるパスに&code(){library};というディレクトリを配置する。

ディレクトリ構成は以下。

#geshi{{{
$ tree
.
├── library
│   └── hello.py
└── test.yaml

1 directory, 2 files
}}}

''test.yaml''

#geshi(yaml){{{
- hosts: localhost
  connection: local
  gather_facts: no
  tasks:
    - hello: echo=world
}}}


プレイブックを実行する。(簡単のためconnectionはlocal)

#geshi{{{
$ ls
library  test.yaml
$ ansible-playbook test.yaml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [localhost] **********************************************************************************

TASK [hello] **************************************************************************************
ok: [localhost] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "message": "world"}

PLAY RECAP ****************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

}}}

&label(sample){サンプル};&ref(playbooks_test.tar.gz);
*** &label(study){実験}; 特定のroleに適用する [#sd1cad44]

roleの下に&code(){library};というディレクトリを配置する。

#geshi{{{
$ tree
.
├── db.yaml
├── roles
│   ├── db
│   │   └── tasks
│   │       └── main.yaml
│   └── web
│       ├── library
│       │   └── hello.py
│       └── tasks
│           └── main.yaml
└── web.yaml

6 directories, 5 files
}}}

''web.yaml''

#geshi(yaml){{{
- hosts: localhost
  connection: local
  gather_facts: no
  roles:
    - web
}}}

''roles/web/tasks/main.yaml''
#geshi(yaml){{{
- hello: echo=web
}}}

''db.yaml''

#geshi(yaml){{{
- hosts: localhost
  connection: local
  gather_facts: no
  roles:
    - db
}}}

''roles/db/tasks/main.yaml''
#geshi(yaml){{{
- hello: echo=db
}}}

プレイブックを実行する。

#geshi{{{
$ ansible-playbook web.yaml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [localhost] **********************************************************************************

TASK [web : hello] ********************************************************************************
ok: [localhost] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "message": "web"}

PLAY RECAP ****************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

}}}

roleがdbの場合はmoduleのロードに失敗しエラーとなる。

#geshi{{{
$ ansible-playbook db.yaml -v
Using /etc/ansible/ansible.cfg as config file
ERROR! couldn't resolve module/action 'hello'. This often indicates a misspelling, missing collection, or incorrect module path.

The error appears to be in '/test/ansible/module_test/roles_test/roles/db/tasks/main.yaml': line 1, column 3, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:


- hello: echo=db
  ^ here
}}}

&label(sample){サンプル}; &ref(./roles_test.tar.gz,100%);

&label(warn){参考};
- https://docs.ansible.com/ansible/latest/dev_guide/developing_locally.html#developing-locally
- https://docs.ansible.com/ansible/latest/reference_appendices/config.html
* 参考リンク [#k3ccef05]

- moduleの開発
-- https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
-- https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html
-- https://docs.ansible.com/ansible/latest/dev_guide/overview_architecture.html#id1
- moduleの追加とロード
-- https://docs.ansible.com/ansible/latest/dev_guide/developing_locally.html#developing-locally
- Ansible Config
-- https://docs.ansible.com/ansible/latest/reference_appendices/config.html



トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP