0
0

More than 3 years have passed since last update.

PythonからAPIを使ってAnsibleを実行する

Posted at

初めに

Ansibleって便利ですよね。同じ作業をたくさんのサーバで行う時は神様だと思います。

Playbookを叩くだけでも十分自動化している感ありますが、Playbookの実行も良い感じに自動化したいと思いました。
シェルスクリプトを書いても良いんですが、結果などをパースするのは面倒です。

そこで今回は、Pythonから良い感じにAnsibleを叩いてみたいと思います。
意外と記事が見つからなかったです。

ソースコード

__main__から読むとわかりやすいです。
192.168.0.1ls -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を叩きたいと思ってこの方法を探してました。
他の方のお役に立てれば何よりです。

参考

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0