この投稿はAnsible 2 Advent Calendar 2019(通称裏アドベントカレンダー?)の13日めの記事です。
1週間全部俺の6回目です!
今回はシェルスクリプトでAnsibleモジュールを作ってみようと思います。
シェルスクリプト
どうやるの?
Ansibleは標準だとPythonでモジュールが作成されていますが、他の言語で作成することができます。
例えばAnsible Advent Calendar 2019の2日目の記事はGoでモジュールを作る例でした。
こちらの記事で述べられている通り処理結果はJSONで返すことと、第一引数でパラメーターが記載されているファイルパスが渡されるので、それを読み込んでパースが出来れば何の言語でも大丈夫です。
パラメーターファイルの中身
| 
					 1 2  | 
						name=test state=present _ansible_check_mode=False _ansible_no_log=False _ansible_debug=False _ansible_diff=False _ansible_verbosity=0 _ansible_version=2.9.2 _ansible_module_name=dir _ansible_syslog_facility=LOG_USER _ansible_selinux_special_fs='['"'"'fuse'"'"', '"'"'nfs'"'"', '"'"'vboxsf'"'"', '"'"'ramfs'"'"', '"'"'9p'"'"', '"'"'vfat'"'"']' _ansible_string_conversion_action=warn _ansible_socket=None _ansible_shell_executable=/bin/sh _ansible_keep_remote_files=False _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1576075509.363012-157708934303795/ _ansible_remote_tmp='~/.ansible/tmp'  | 
					
今回はディレクトリを操作するモジュールをシェルスクリプトで作ってみました。
ディレクトリを操作するモジュール
パラメーターは以下のものが使用できます。
| パラメーター | 説明 | 
|---|---|
| name | ディレクトリ名 | 
| path | ディレクトリを作成するパス | 
| state | present or absent | 
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99  | 
						#!/bin/bash # # This code is ansible module example using bash. function exit_json() {     if [ -n "$2" ] ; then         json_structure="             {                 \"changed\": $1,                 $2             }         "     else         json_structure="             {                 \"changed\": $1             }         "     fi     echo $json_structure     exit 0 } function fail_json() {     json_structure="         {             \"failed\": "true",             \"msg\": \"$1\"         }     "     echo $json_structure     exit 1 } function main() {     # arg parse     arg_flag=0     for arg in $(cat $1) ; do         echo $arg | grep -E ',|\[|\]' > /dev/null 2>&1         if [ $? -eq 0 ] ; then             arg2="$arg2$arg"             arg_flag=1             continue         fi         if [ $arg_flag -eq 0 ] ; then             key=`echo $arg | cut -d '=' -f 1`             value=`echo $arg | cut -d '=' -f 2`             declare "module_arg_$key=$value"             arg_flag=0         else             key=`echo $arg2 | cut -d '=' -f 1`             value=`echo $arg2 | cut -d '=' -f 2`             declare "module_arg_$key=$value"             arg_flag=0         fi     done     # parameters     name=$module_arg_name     path=$module_arg_path     state=$module_arg_state     # When state is present     if [ $state == "present" -a -n "$name" -a -n "$path" ] ; then         if [ -d $path ] ; then             if [ -d "$path/$name" ] ; then                 exit_json "false"             else                 result=`mkdir "$path/$name" 2>&1`                 if [ -n "$result" ] ; then                     fail_json "`echo -n $result`"                 else                     exit_json "true" "\"directory_path\": \"$path/$name\""                 fi             fi         else             fail_json "Error: not found $module_arg_path"         fi     fi     # When state is absent     if [ $state == "absent" -a -n "$name" -a -n "$path" ] ; then         if [ -d "$path/$name" ] ; then             rm -rf "$path/$name" 2>&1 > /dev/null             if [ -d "$path/$name" ] ; then                 fail_json "Error: failed delete folder $path/$name"             fi             exit_json "true" "\"directory_path\": \"$path/$name\""         else             exit_json "false"         fi     fi     fail_json "Required parameters include: name, path, state" } main $1  | 
					
上記スクリプトを作成します。
| 
					 1 2 3 4  | 
						(venv) [root@localhost shell]# mkdir library/ (venv) [root@localhost shell]# vi library/dir.sh (スクリプトを貼り付ける)  | 
					
Playbook
作成
Playbookを作成します。
| 
					 1 2  | 
						(venv) [root@localhost shell]# vi main.yml  | 
					
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						--- - name: Directory operation playbook   hosts: localhost   gather_facts: no   tasks:     - dir:         name: hoge         path: /opt         state: present  | 
					
実行
Playbookを実行します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						(venv) [root@localhost shell]# ansible-playbook main.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Directory operation playbook] ******************************************************************************************************************************************* TASK [dir] ******************************************************************************************************************************************************************** changed: [localhost] PLAY RECAP ******************************************************************************************************************************************************************** localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 (venv) [root@localhost shell]# ls /opt/ hoge  | 
					
ディレクトリが作成されていることが確認できます。
もう一度実行します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						(venv) [root@localhost shell]# ansible-playbook main.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Directory operation playbook] ******************************************************************************************************************************************* TASK [dir] ******************************************************************************************************************************************************************** ok: [localhost] PLAY RECAP ******************************************************************************************************************************************************************** localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  | 
					
冪統制で既にディレクトリが存在しているため changed は発生しません。
ちなみに、registerを使った場合は以下のように directory_path に作成されたディレクトリのパスが表示されます。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  | 
						(venv) [root@localhost shell]# ansible-playbook main.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Directory operation playbook] ******************************************************************************************************************************************* TASK [dir] ******************************************************************************************************************************************************************** changed: [localhost] TASK [debug] ****************************************************************************************************************************************************************** ok: [localhost] => {     "result": {         "changed": true,         "directory_path": "/opt/hoge",         "failed": false     } } PLAY RECAP ******************************************************************************************************************************************************************** localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  | 
					
次に state を absent に変更して削除してみます。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						(venv) [root@localhost shell]# ansible-playbook main.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Directory operation playbook] ******************************************************************************************************************************************* TASK [dir] ******************************************************************************************************************************************************************** changed: [localhost] PLAY RECAP ******************************************************************************************************************************************************************** localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 (venv) [root@localhost shell]# ls /opt/ (venv) [root@localhost shell]#  | 
					
ディレクトリが削除されていることが確認できます。
もう一度実行します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						(venv) [root@localhost shell]# ansible-playbook main.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Directory operation playbook] ******************************************************************************************************************************************* TASK [dir] ******************************************************************************************************************************************************************** ok: [localhost] PLAY RECAP ******************************************************************************************************************************************************************** localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  | 
					
こちらも冪統制で既にディレクトリが削除されているので changed が発生しません。
オプションが足りない場合は以下のようにエラーが発生します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						(venv) [root@localhost shell]# ansible-playbook main.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Directory operation playbook] ******************************************************************************************************************************************* TASK [dir] ******************************************************************************************************************************************************************** fatal: [localhost]: FAILED! => {"changed": false, "msg": "Required parameters include: name, path, state"} PLAY RECAP ******************************************************************************************************************************************************************** localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0  | 
					
最後に
今回シェルスクリプトでAnsibleモジュールを作ってみましたが正直ツライw
ただ、冪統制の処理を書くことと戻りをJSONで返すだけで色々な言語で作れちゃうので便利ですね 🙂