#author("2020-03-08T01:30:03+09:00","default:haikikyou","haikikyou")
#author("2020-03-09T23:52:23+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で終了
*** 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
- debugger
-- https://docs.ansible.com/ansible/latest/user_guide/playbooks_debugger.html