初めに
Ansibleって便利ですよね。同じ作業をたくさんのサーバで行う時は神様だと思います。
Playbookを叩くだけでも十分自動化している感ありますが、Playbookの実行も良い感じに自動化したいと思いました。
シェルスクリプトを書いても良いんですが、結果などをパースするのは面倒です。
そこで今回は、Pythonから良い感じにAnsibleを叩いてみたいと思います。
意外と記事が見つからなかったです。
ソースコード
__main__
から読むとわかりやすいです。
192.168.0.1
でls -la /
を実行するPlaybookです。
Ansibleをコマンドで使ったことがあれば簡単に読めると思います。
所々簡単なコメントを入れているのでご参考に。
pip3 install ansible
import json
import shutil
from ansible.module_utils.common.collections import ImmutableDict
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
from ansible import context
import ansible.constants as C
class ResultCallback(CallbackBase):
def __init__(self, *args, **kwargs):
super(ResultCallback, self).__init__(*args, **kwargs)
self.host_ok = {}
self.host_unreachable = {}
self.host_failed = {}
def v2_runner_on_unreachable(self, result):
host = result._host
self.host_unreachable[host.get_name()] = result
def v2_runner_on_ok(self, result, *args, **kwargs):
host = result._host
self.host_ok[host.get_name()] = result
def v2_runner_on_failed(self, result, *args, **kwargs):
host = result._host
self.host_failed[host.get_name()] = result
def ansible_run(play_source, host_list):
# ansible-playbookで指定できる引数と同じ
context.CLIARGS = ImmutableDict(
tags={},
listtags=False,
listtasks=False,
listhosts=False,
syntax=False,
connection='ssh',
module_path=None,
forks=100,
private_key_file=None,
ssh_common_args=None,
ssh_extra_args=None,
sftp_extra_args=None,
scp_extra_args=None,
become=False,
become_method='Sudo',
become_user='root',
verbosity=True,
check=False,
start_at_task=None
)
# 鍵認証が優先され、パスワードを聞かれた場合のみ利用する。書きたくない場合は適当でOK
passwords = dict(vault_pass='secret')
# コールバックのインスタンス化
results_callback = ResultCallback()
# インベントリを1ライナー用フォーマットに変換
sources = ','.join(host_list)
if len(host_list) == 1:
sources += ','
loader = DataLoader()
inventory = InventoryManager(loader=loader, sources=sources)
# 値をセット
variable_manager = VariableManager(loader=loader, inventory=inventory)
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)
# 実行
tqm = None
try:
tqm = TaskQueueManager(
inventory=inventory,
variable_manager=variable_manager,
loader=loader,
passwords=passwords,
stdout_callback=results_callback,
)
result = tqm.run(play)
finally:
# 終了後に一時ファイルを削除している
if tqm is not None:
tqm.cleanup()
# Remove ansible tmpdir
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
return results_callback
if __name__ == "__main__":
# 実行ホストを指定(インベントリにも指定される)
host_list = [ "root@192.168.0.1" ]
# プレイブックを定義
play_source = dict(
name = "Ansible Play",
hosts = host_list,
gather_facts = 'no',
tasks = [
dict(action=dict(module='shell', args='ls -l /'), register='shell_out')
]
)
results = ansible_run(play_source=play_source, host_list=host_list)
for host, result in results.host_ok.items():
print(host)
print(json.dumps(result._result, indent=4))
for host, result in results.host_failed.items():
print(host)
print(json.dumps(result._result, indent=4))
for host, result in results.host_unreachable.items():
print(host)
print(json.dumps(result._result, indent=4))
実行結果の辞書はこんな感じで取得できます。以下のはJsonで出力したものを抜粋してます。
{
"cmd": "ls -l /",
"stdout": "total 28\nlrwxrwxrwx. 1 root root 7 May 11 2019 bin -> usr/bin\ndr-xr-xr-x. 6 root root 4096 Nov 12 18:17 boot\ndrwxr-xr-x. 7 root root 65 Nov 17 00:41 data\ndrwxr-xr-x. 21 root root 3580 Nov 23 12:10 dev\ndrwxr-xr-x. 104 root root 8192 Nov 22 14:11 etc\ndrwxr-xr-x. 6 root root 4096 Nov 20 13:06 gvolume0\ndrwxr-xr-x. 3 root root 4096 Nov 17 00:47 gvolume1\ndrwxr-xr-x. 3 root root 19 Nov 10 01:24 home\nlrwxrwxrwx. 1 root root 7 May 11 2019 lib -> usr/lib\nlrwxrwxrwx. 1 root root 9 May 11 2019 lib64 -> usr/lib64\ndrwxr-xr-x. 2 root root 6 May 11 2019 media\ndrwxr-xr-x. 2 root root 6 May 11 2019 mnt\ndrwxr-xr-x. 2 root root 6 May 11 2019 opt\ndr-xr-xr-x. 1056 root root 0 Nov 22 14:04 proc\ndr-xr-x---. 4 root root 192 Nov 23 11:27 root\ndrwxr-xr-x. 32 root root 1100 Nov 22 14:46 run\nlrwxrwxrwx. 1 root root 8 May 11 2019 sbin -> usr/sbin\ndrwxr-xr-x. 2 root root 6 May 11 2019 srv\ndr-xr-xr-x. 13 root root 0 Nov 22 14:04 sys\ndrwxrwxrwt. 9 root root 212 Nov 23 22:51 tmp\ndrwxr-xr-x. 12 root root 144 Nov 10 01:22 usr\ndrwxr-xr-x. 21 root root 4096 Nov 10 01:28 var",
"stderr": "",
"rc": 0,
"start": "2020-11-23 22:51:11.787866",
"end": "2020-11-23 22:51:11.793951",
"delta": "0:00:00.006085",
"changed": true,
"invocation": {
"module_args": {
"_raw_params": "ls -l /",
"_uses_shell": true,
"warn": true,
"stdin_add_newline": true,
"strip_empty_ends": true,
"argv": null,
"chdir": null,
"executable": null,
"creates": null,
"removes": null,
"stdin": null
}
},
"stdout_lines": [
"total 28",
"lrwxrwxrwx. 1 root root 7 May 11 2019 bin -> usr/bin",
"dr-xr-xr-x. 6 root root 4096 Nov 12 18:17 boot",
"drwxr-xr-x. 7 root root 65 Nov 17 00:41 data",
"drwxr-xr-x. 21 root root 3580 Nov 23 12:10 dev",
"drwxr-xr-x. 104 root root 8192 Nov 22 14:11 etc",
"drwxr-xr-x. 6 root root 4096 Nov 20 13:06 gvolume0",
"drwxr-xr-x. 3 root root 4096 Nov 17 00:47 gvolume1",
"drwxr-xr-x. 3 root root 19 Nov 10 01:24 home",
"lrwxrwxrwx. 1 root root 7 May 11 2019 lib -> usr/lib",
"lrwxrwxrwx. 1 root root 9 May 11 2019 lib64 -> usr/lib64",
"drwxr-xr-x. 2 root root 6 May 11 2019 media",
"drwxr-xr-x. 2 root root 6 May 11 2019 mnt",
"drwxr-xr-x. 2 root root 6 May 11 2019 opt",
"dr-xr-xr-x. 1056 root root 0 Nov 22 14:04 proc",
"dr-xr-x---. 4 root root 192 Nov 23 11:27 root",
"drwxr-xr-x. 32 root root 1100 Nov 22 14:46 run",
"lrwxrwxrwx. 1 root root 8 May 11 2019 sbin -> usr/sbin",
"drwxr-xr-x. 2 root root 6 May 11 2019 srv",
"dr-xr-xr-x. 13 root root 0 Nov 22 14:04 sys",
"drwxrwxrwt. 9 root root 212 Nov 23 22:51 tmp",
"drwxr-xr-x. 12 root root 144 Nov 10 01:22 usr",
"drwxr-xr-x. 21 root root 4096 Nov 10 01:28 var"
],
"stderr_lines": [],
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"_ansible_no_log": false
}
終わりに
結果もPythonで使いやすいので他のシステムとの結合も簡単です。
私は趣味で書いているPythonのシステムからAnsibleを叩きたいと思ってこの方法を探してました。
他の方のお役に立てれば何よりです。