LoginSignup
8
2

More than 1 year has passed since last update.

Ansibleの実行結果の出力をデフォルト出力 + JSONファイルにしたい

Last updated at Posted at 2022-06-01

Ansibleの実行結果をJSONで扱いたい! が、コンソール出力が微妙!

通常、Ansibleは実行結果を段階ごとに下記のようにコンソールに出力していきますが、

$ ansible-playbook -i hosts playbook.yml

PLAY [all] ******************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************
ok: [web_server]
ok: [db_server]

....

PLAY RECAP ******************************************************************************************************
db_server                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web_server                 : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

この実行結果を他のツールで扱いたい場合、構造化されたデータ(JSONなど)が欲しくなってくると思います。(もしくは、このままコンソール出力を解析してもいいですが、かなり辛いです。)

そういった場合は ANSIBLE_STDOUT_CALLBACK=json を環境変数として与えてあげればいいのですが、下記のような長いJSON出力がプレイブック実行の 最後にドバッと一気に出ます。

$ ANSIBLE_STDOUT_CALLBACK=json ansible-playbook -i hosts playbook.yml
{
    "custom_stats": {},
    "global_custom_stats": {},
    "plays": [
        {
            "play": {
                "duration": {
                    "end": "2022-05-26T04:22:39.631342Z",
                    "start": "2022-05-26T04:22:37.698472Z"
                },

... (4200行ののち)

    "stats": {
        "db_server": {
            "changed": 0,
            "failures": 0,
            "ignored": 0,
            "ok": 5,
            "rescued": 0,
            "skipped": 0,
            "unreachable": 0
        },
        "web_server": {
            "changed": 0,
            "failures": 0,
            "ignored": 0,
            "ok": 8,
            "rescued": 0,
            "skipped": 0,
            "unreachable": 0
        }
    }
}
# 上記が ansible-playbook が実行終了のタイミングでドバっと一気に出る

これだと今どこまで処理が進んでいるのかわかりませんね。あと単純に見づらいです。

かといって、JSONをファイルに出力して、標準出力の方はデフォルトの出力方法にする、といった便利な方法はありません。

Ansibleの実行結果出力を自由にしたければ、自作callbackプラグインを作ればOK

ではどうすればいいかというと、Ansibleのコンソール出力をカスタマイズする自作callbackプラグインを作ればOKです。

AnsibleはPythonで出来ており、自分でPythonスクリプトを書くことでAnsibleに自由にプラグインを追加することができます。

そのプロジェクトに閉じたプラグイン(ここでは json_export とします)は下記のように構成すれば作成できます。

ansible.cfg
[defaults]
# ./callback_plugins/json_export.py に自作callbackプラグインを置く
# (ansible-playbook に与える引数のプレイブックからの相対パスになる)
callback_plugins = ./callback_plugins
callbacks_enabled = json_export

# デフォルトのcallbackプラグインの指定
# 別に ANSIBLE_STDOUT_CALLBACK=json_export のように実行時に環境変数として指定してもいい
stdout_callback = json_export
callback_plugins/json_export.py
import os
import json
from ansible.parsing.ajson import AnsibleJSONEncoder
from ansible.plugins.callback.default import CallbackModule as Base

DOCUMENTATION = '''
    name: json_export
    type: stdout
    short_description: Also export results into a JSON file if specified.
    version_added: historical
    description:
        - Export results into a JSON file (specified at JSON_EXPORT_PATH).
    extends_documentation_fragment:
      - default_callback
    requirements:
      - set as stdout in configuration
'''

# 基本的にはデフォルトの出力が欲しいので、デフォルトのcallbackモジュールを拡張したものを作成する
class CallbackModule(Base):

    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'json_export'

    def __init__(self):
        super(CallbackModule, self).__init__()
        self._results = []
        self._servers = {}
        self._output_to = os.getenv('JSON_EXPORT_PATH', '')

    # v2_* メソッドをオーバーライドしてデフォルトの動作に処理を追加していく

    def v2_playbook_on_task_start(self, task, is_conditional):
        super(CallbackModule, self).v2_playbook_on_task_start(task, is_conditional)
        self._results.append({ 'name': task.get_name(), 'items': [] })
    
    def v2_runner_on_ok(self, result):
        super(CallbackModule, self).v2_runner_on_ok(result)
        self._append_result('ok', result)

    def v2_runner_on_failed(self, result, ignore_errors=False):
        super(CallbackModule, self).v2_runner_on_failed(result)
        self._append_result('failed', result)

    def v2_runner_on_skipped(self, result):
        super(CallbackModule, self).v2_runner_on_skipped(result)
        self._append_result('skipped', result)

    def v2_runner_on_unreachable(self, result):
        super(CallbackModule, self).v2_runner_on_skipped(result)
        self._append_result('unreachable', result)

    def _append_result(self, result_type, result):
        lastResults = self._results[-1]['items']
        host_name = result._host.get_name()

        rawResult = result._result
        essentialResult = {}
        for key in ["changed", "start", "end", "stdout", "stdout_lines", "stderr", "stderr_lines"]:
            if key in rawResult:
                essentialResult[key] = rawResult[key]

        lastResults.append({
            'host_name': host_name,
            'type': result_type,
            'result': essentialResult
        })
        self._servers[host_name] = True

    # 実行結果出力時(実行の最後)にJSONファイルを出力する
    def v2_playbook_on_stats(self, stats):
        super(CallbackModule, self).v2_playbook_on_stats(stats)
        if self._output_to:
            with open(self._output_to, "w") as f:
                json.dump({
                    'results': self._results,
                    'host_names': list(self._servers.keys())
                }, f, cls=AnsibleJSONEncoder, indent=2, ensure_ascii=False, sort_keys=True)

ansible.cfg でプラグインのサーチパスと許可するプラグインの名前を追加したのち、それを実行時に使うプラグインに設定するだけです。

上記自作プラグインで要求している JSON_EXPORT_PATH を与えてあげれば、実行の最後にJSONファイルが出力されます。

$ JSON_EXPORT_PATH=ansible_result.json ansible-playbook -i hosts playbook.yml
PLAY [all] *************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************
ok: [web_server]
ok: [db_server]

...

PLAY RECAP *************************************************************************************************************************
db_server                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web_server                 : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible_result.json
{
  "host_names": [
    "db_server",
    "web_server"
  ],
  "results": [
    {
      "items": [
        {
          "host_name": "db_server",
          "result": {
            "changed": false
          },
          "type": "ok"
        },
...

まとめ

Ansibleで実行結果の出力を自由にカスタマイズしたい場合はcallbackプラグインを作れば何でもできます。

これを応用して、たとえば、Ansibleの全実行結果を強制的に監査用のリポジトリにアップロードするといったことも可能です。

8
2
0

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
8
2