zaki work log

作業ログやら生活ログやらなんやら

[Ansible] YAML/JSON書式の文字列を構造化データとして扱う

文字列として保持しているYAMLJSON形式のデータを構造化データとして扱うにはフィルタで変換するのが簡単です。
PowerShellConvertFrom-Jsonとかと同じやつです。

docs.ansible.com

YAML形式の文字列をオブジェクトに変換

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: sample-http
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-http
  template:
    metadata:
      labels:
        app: sample-http
    spec:
      containers:
      - image: httpd
        name: httpd

こんな感じのよく見かけるYAML形式のデータがありこれをstring型(plain/text)で変数に持っている場合に、構造化データとして処理するにはfrom_yamlフィルタを使うと簡単に処理できます。

以下はcatで読み込んで__resultにセットしたYAML形式の文字列データをfrom_yamlフィルタでdata変数にセット、あとは普通にdata変数を構造化データとして扱って辞書型やリスト参照しています。

- hosts: localhost
  gather_facts: no

  tasks:
  - name: get yaml file
    command: cat yamlfile.yml
    register: __result

  - name: get yaml value from text
    vars:
      data: "{{ __result.stdout | from_yaml }}"
    debug:
      msg: '{{ data.spec.template.spec.containers[0].image }}'

このPlaybookでansible-playbookを実行すると以下の通り。

TASK [get yaml value from text] ***********************************************
ok: [localhost] => 
  msg: httpd

JSON形式の文字列をオブジェクトに変換

JSON形式の場合も同様にfrom_jsonを使えば同じように処理できます。

{
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {
        "labels": {
            "app": "sample-http"
        },
        "name": "sample-http",
        "namespace": "default"
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "sample-http"
            }
        },
        "template": {
            "metadata": {
                "labels": {
                    "app": "sample-http"
                }
            },
            "spec": {
                "containers": [
                    {
                        "image": "httpd",
                        "name": "httpd"
                    }
                ]
            }
        }
    }
}

このJSON(内容は前述のYAMLと同じ)に対して

  - name: get json file
    #command: curl https://ifconfig.io/all.json
    command: cat jsonfile.json
    register: __result

  - name: get json value from text
    vars:
      data: "{{ __result.stdout | from_json }}"
    debug:
      msg: "{{ data.spec.template.spec.containers[0].image }}"

この内容のPlaybookを実行すると以下の通り。

TASK [get json value from text] ***********************************************
ok: [localhost] => 
  msg: httpd

フィルターの結果の辞書やリストから直接値を取り出す

{{ result | from_json['spec'] }}とかは出来ないので、指定要素を取り出すフィルタが無いか探したけど処理が単純すぎるのか逆に見当たらず。。
結局カッコ使ってこんな感じに。。うまいやり方は他にあるのかな。。

    debug:
      msg: "{{ (__result.stdout | from_json).spec.template.spec.containers[0].image }}"

追記: attr()使えないかと思ったけどダメみたい。現状カッコしかないかなー


まとめ

catで読み込んだりしただけだと、結果のJSONYAML形式のデータは、書式はYAMLですが単なる複数行文字列データなので__result.stdout.data.spec ...のように構造化データとして処理は出来ません。
from_yamlフィルタなどを使うことで構造化データになるので、特定要素へのアクセスが簡単に処理できるようになります。
また、構造化データであればjson_query()のようなフィルタを使った処理も可能になります。

確認した環境は以下の通り。

$ ansible --version
ansible 2.10.3
  config file = /home/zaki/src/ansible-sample/yaml-pickup/ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/esxi-ansible/venv/2.10/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/esxi-ansible/venv/2.10/bin/ansible
  python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]