サカナトラ

自動化したり統計したり

YAMLからもっとAnsible Playbookを理解する(その他編)

YAMLの公式ドキュメント等を見て面白いなと思ったところのメモ

Flow Style

YAMLでは配列、キーバリューのデータ構造について記述するときに、インデントを活用したYAMLではなくインジケーターを使用した形でも記述が可能です。

- [ 1, 2, 3 ]
- [ a, b, c ]
- [ x, y, z ]

これはすなわち、、、 以下の形と同義ということになるようです。

- 
  - 1
  - 2
  - 3
-
  - a
  - b
  - c
- 
  - x
  - y
  - z

Ansibleで確認

プレイブック

---
- hosts: localhost
  gather_facts: no

  vars:
    demo1:
      - [1, 2, 3]
      - [a, b, c]
      - [x, y, z]
    demo2:
      - 
        - 1
        - 2
        - 3
      - 
        - a
        - b
        - c
      - 
        - x
        - y
        - z

  tasks:
    - name: debug demo1
      debug:
        var: demo1
      
    - name: debug demo2
      debug:
        var: demo2

実行結果

PLAY [localhost] ****************************************************************************************************************************************************************************************

TASK [debug demo1] **************************************************************************************************************************************************************************************
ok: [localhost] => {
    "demo1": [
        [
            1,
            2,
            3
        ],
        [
            "a",
            "b",
            "c"
        ],
        [
            "x",
            "y",
            "z"
        ]
    ]
}

TASK [debug demo2] **************************************************************************************************************************************************************************************
ok: [localhost] => {
    "demo2": [
        [
            1,
            2,
            3
        ],
        [
            "a",
            "b",
            "c"
        ],
        [
            "x",
            "y",
            "z"
        ]
    ]
}

PLAY RECAP **********************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

以上の結果をもって2つの書き方がAnsibleの処理においても同義になっていることがわかりました。

キーバリュー構造についても同様に下記の2つは同義ということになります。

a: b
{a: b}

ansibleで実行してみる プレイブック

---
- hosts: localhost
  gather_facts: no

  vars:
    demo1: {first: 1, second: 2, third: 3}
    demo2:
      first: 1
      second: 2
      third: 3

  tasks:
    - name: debug demo1
      debug:
        var: demo1
      
    - name: debug demo2
      debug:
        var: demo2

結果 (表示時にキーでソートされています。)

PLAY [localhost] *************************************************************************************************************************************************************

TASK [debug demo1] ***********************************************************************************************************************************************************
ok: [localhost] => {
    "demo1": {
        "first": 1,
        "second": 2,
        "third": 3
    }
}

TASK [debug demo2] ***********************************************************************************************************************************************************
ok: [localhost] => {
    "demo2": {
        "first": 1,
        "second": 2,
        "third": 3
    }
}

PLAY RECAP *******************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 

キーバリューの方もこれで2つの書き方が同義であることが確認できました。

ちなみにAnsibleの方のドキュメントにはこれらはフローコレクションという形で書かれていました。

YAML Syntax — Ansible Documentation

Block Style

先ほどのFlow Styleが複数行にまたがる構造を1行にまとめられるものであるのに対して、Block Styleは基本的には1行で書かれるものを複数行に分けて書くことができるようになるというものです。

Block Scalar

:の後につけるインジケーターによって微妙に改行の扱いが異なります。

  • |の場合
    改行をそのまま取り扱います。
sample: |
  a
  b

debugモジュールで参照した結果

ok: [localhost] => {
    "sample": "a\nb\n"
}
  • >の場合
    文中の改行は折りたたまれてスペースになります。
sample: >
  a
  b

debugモジュールで参照した結果

ok: [localhost] => {
    "sample": "a b\n"
}

Block Folding

ただし改行が折りたたまれるパターンは行頭が文字で始まるときの直前の改行となるので、改行を2つ空けたり、スペースで始まる行については>を使った場合でも改行が入ります。

sample1: >
  a

  b
# "a\nb"

sample2: >
  a
   b
# "a\n b"

ansible実行結果

プレイブック

---
- hosts: localhost
  gather_facts: false

  vars: 
    sample1: >
      a

      b

    sample2: >
      a
       b
  tasks:
    - debug:
        var: sample1
    - debug:
        var: sample2

実行結果

PLAY [localhost] ********************************************************************************************************************************************************************************************************

TASK [debug] ************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "sample1": "a\nb\n"
}

TASK [debug] ************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "sample2": "a\n b\n"
}

PLAY RECAP **************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

Explicit Block Mapping Entries

先ほどのBlock Scalarのように複数行で一つとなる定義をキーバリューの構造でも行うことができます。 ?をインジケーターとして用いるとキーとバリューの間に改行があってもインデントがあっていれば正しく認識することができます。

simple: ? a:
          b
# { "a": "b" }

complex: ? a:
          ? b:
            [1, 2, 3]
# { "a":  { "b": [1, 2, 3] } }

ansible実行結果 プレイブック

---
- hosts: localhost
  gather_facts: false
  vars:
    simple:
      ? a
      : b
    complex:
      ? a
      : ? b
        : [1, 2, 3]
  
  tasks:
    - debug:
        var: simple
    - debug:
        var: complex

実行結果

PLAY [localhost] ************************************************************************************************************************************************

TASK [debug] ****************************************************************************************************************************************************
ok: [localhost] => {
    "simple": {
        "a": "b"
    }
}

TASK [debug] ****************************************************************************************************************************************************
ok: [localhost] => {
    "complex": {
        "a": {
            "b": [
                1,
                2,
                3
            ]
        }
    }
}

PLAY RECAP ******************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Tag Prefix

yamlでは変数の型を宣言せずとも自動的に解釈し、型をつけてくれます。

first: 1 # => 整数型
point: 0.2 # => 浮動小数点型
gather_facts: yes # => bool型
name: do something # => 文字列型

ここでTag Prefixを使うと明示的な型付けができます。

string_yes: !!str yes # => 明示的に文字列型になる
type_float:  !!float 1.2 # => 明示的に浮動小数点型になる

Tag Prefixを使う際には!!の後に型名を入れることで明示的な指定を行うことができます。 YAMLではTagはRFC4151のTag URIスキーマを参照する形になっています。 デフォルトで!!にはtag:yaml.org,2002:が当たっているので!!str!!intはtag:yaml.org,2002:strtag:yaml.org,2002:intを参照しています。 またTagは!`の形で参照することもできる他、Tagディレクティブで宣言することもできます。なので下記の形でもTagを使うことができます。
詳しくは公式を参照してください⇒ YAML Ain’t Markup Language (YAML™) revision 1.2.2

%TAG !e! tag:yaml.org,2002:
---
foo: !<tag:yaml.org,2002:str> bar
hoge: !e! map { piyo: 1}

ansible 実行結果 プレイブック

%TAG !e! tag:yaml.org,2002:
---
- hosts: localhost
  gather_facts: no
  vars:
    not_tag:
      - 12345
      - 1.0
      - 1
    not_tag_date: 2022-01-01
    tag_var: 
      - !!str 12345
      - !!float 1.0
      - !<tag:yaml.org,2002:str> 1 
      - !e!str 2022-01-01

  tasks:
    - name: not tag
      block:
        - debug:
            msg: "{{ item | type_debug }}"
          loop: "{{ not_tag }}"

        - debug:
            msg: "{{ not_tag_date | type_debug }}"

    - name: debug vars with tag
      debug:
        msg: "{{ item | type_debug }}"
      loop: "{{ tag_var }}"

実行結果

PLAY [localhost] *******************************************************************************************************************************************

TASK [debug] ***********************************************************************************************************************************************
ok: [localhost] => (item=12345) => {
    "msg": "int"
}
ok: [localhost] => (item=1.0) => {
    "msg": "float"
}
ok: [localhost] => (item=1) => {
    "msg": "int"
}

TASK [debug] ***********************************************************************************************************************************************
ok: [localhost] => {
    "msg": "date"
}

TASK [debug vars with tag] *********************************************************************************************************************************
ok: [localhost] => (item=12345) => {
    "msg": "str"
}
ok: [localhost] => (item=1.0) => {
    "msg": "float"
}
ok: [localhost] => (item=1) => {
    "msg": "str"
}
ok: [localhost] => (item=2022-01-01) => {
    "msg": "str"
}

PLAY RECAP *************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Anchor Alias

&*を使って値の再利用ができます。 下記の通りほとんど変数のように使用が可能です。

foo: &test bar
hoge: *test # => 値は"bar"となる

Anchorはスカラ値だけでなくキーバリューや配列でも使用可能です。 このAnchorはYAMLが持つ機能なのでAnsibleでは変数として取り扱えないようなものも再利用できます。

---
- hosts: localhost
  gather_facts: false

  vars:
    sample: &default happy
    feeling: *default

  tasks:
    - &debug_task
      name: debug sample
      debug:
        var: sample

    - name: debug feeling
      debug:
        var: feeling
    
    - *debug_task

上記のプレイブックを実行するとdebug sampleのタスクが*debug_taskの箇所で実行されます。

PLAY [localhost] ********************************************************************************************************************************************************************************

TASK [debug sample] *****************************************************************************************************************************************************************************
ok: [localhost] => {
    "sample": "happy"
}

TASK [debug feeling] ****************************************************************************************************************************************************************************
ok: [localhost] => {
    "feeling": "happy"
}

TASK [debug sample] *****************************************************************************************************************************************************************************
ok: [localhost] => {
    "sample": "happy"
}

PLAY RECAP **************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

またキーバリューと配列のAnchoraは<<:で展開もできます。 これで展開すると、キーバリューの場合は値の上書きができます。 感覚的にはJavaScriptでスプレッド構文を使ったオブジェクトのプロパティ上書きに近いですね。

- &debug_task
  name: debug sample
  debug:
    var: sample

- <<: *debug_task
  name: debug sample2
# nameの値を入れ替え(下記の構造になる)
# - name: debug sample2
#   debug:
#     var: sample

Ansible 実行結果 プレイブック

---
- hosts: localhost
  gather_facts: false
  vars:
    sample: hello!

  tasks:
    - &debug_task
      name: debug sample
      debug:
        var: sample

    - <<: *debug_task
      name: debug sample2

実行結果

PLAY [localhost] ************************************************************************************************************************************************

TASK [debug sample] *********************************************************************************************************************************************
ok: [localhost] => {
    "sample": "hello!"
}

TASK [debug sample2] ********************************************************************************************************************************************
ok: [localhost] => {
    "sample": "hello!"
}

PLAY RECAP ******************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

docker_compose.ymlですがNetBoxのdocker_compose.ymlはAnchorがいい感じに使われているので一見の価値あるかと、、、

Anchor、AliasはYAMLの形式を活かした形だと思うので今後のPlaybook開発に活用することが出来ればよいかなと思いました。 それ以外はあまり使いどころとしては微妙かも、、、

参考

YAML Ain’t Markup Language (YAML™) revision 1.2.2

YAML の記述 - CircleCI

YAMLのAnchorとAliasをAnsibleで使う - 赤帽エンジニアブログ

netbox-docker/docker-compose.yml at release · netbox-community/netbox-docker · GitHub