Jinja2 filter - subelements

subelements 這個 filter 是取出 list 裡每個項目裡的 attribute ,然後轉為每個元素為 list 的 list。 xxx 很繞口,對吧,這個不太容易理解,用例子比較好講。

從下面的變數定義可以看到,users 是一個 list,裏面有兩個項目,每個項目代表一個 user

---
users:
  - name: paul
    password: "paul_pass"
    authorized:
      - keys/paul_key1.pub
      - keys/paul_key2.pub
    mysql:
      hosts:
        - "%"
        - "127.0.0.1"
        - "::1"
        - "localhost"
    groups:
      - wheel

  - name: john
    password: "john_pass"
    authorized:
      - keys/john_key.pub
    mysql:
      password: other-mysql-password
      hosts:
        - "utility"
    groups:
      - wheel
      - devops

在經過 subelements() 處理後,會變成以下的資料結構

- - authorized: [keys/paul_key1.pub, keys/paul_key2.pub]
    groups: [wheel]
    mysql:
      hosts: ['%', 127.0.0.1, '::1', localhost]
    name: paul
    password: paul_pass
  - keys/paul_key1.pub

- - authorized: [keys/paul_key1.pub, keys/paul_key2.pub]
    groups: [wheel]
    mysql:
      hosts: ['%', 127.0.0.1, '::1', localhost]
    name: paul
    password: paul_pass
  - keys/paul_key2.pub

- - authorized: [keys/john_key.pub]
    groups: [wheel, devops]
    mysql:
      hosts: [utility]
      password: other-mysql-password
    name: john
    password: john_pass
  - keys/john_key.pub

也就是一個 list,裏面有三個 item;每個 item 又是一個 list,list 裡有兩個元素,第一個是 user,第二個則是 authorized 裡的值。

接下來我們寫一個 playbook 來驗證看看

---
# 檔名: test-sublements.yml
- name: Test subelements
  hosts: all

  vars:
    users:
      - name: paul
        password: "paul_pass"
        authorized:
          - keys/paul_key1.pub
          - keys/paul_key2.pub
        mysql:
          hosts:
            - "%"
            - "127.0.0.1"
            - "::1"
            - "localhost"
        groups:
          - wheel

      - name: john
        password: "john_pass"
        authorized:
          - keys/john_key.pub
        mysql:
          password: other-mysql-password
          hosts:
             - "utility"
        groups:
          - wheel
          - devops

  tasks:
    - name: 用 loop 遍訪每個元素,印出 name 跟 key
      debug:
        msg: "user.name={{ item.0.name }} key={{ item.1 }}"
      loop: "{{ users | subelements('authorized') }}"

    - name: 輸出到 /tmp/subelements_output.yml 再來觀察
      copy:
        content: "{{ users | subelements('authorized') | to_yaml }}"
        dest: /tmp/subelements_output.yml

這個 playbook 只有兩個 task,第一個 task 使用 loop 去遍訪 subelements() 輸出後的 list,這裡印出 item 的第一個元素跟第二個元素。如同前面所提的, 第一個元素是 user,所以 item.0 就等於 user,也就可以直接用 item.0.name 來取得 user.name。 第二個元素是 authorized 裡的值,這邊用 item.1 來存取。

第二個 task 則是把 subelements() 的輸出結果轉為 yaml 檔以後,存到 /tmp/subelements_output.yml

我們執行看看

ansible-playbook -i localhost, -c local test-sublements.yml

輸出結果大略如下:

... 省略 ...
TASK [用 loop 遍訪每個元素,印出 name 跟 key] ****************************************************************************
ok: [localhost] => (item=[{u'mysql': {u'hosts': [u'%', u'127.0.0.1', u'::1', u'localhost']}, u'password': u'paul_pass', u'authorized': [u'keys/paul_key1.pub', u'keys/paul_key2.pub'], u'groups': [u'wheel'], u'name': u'paul'}, u'keys/paul_key1.pub']) => {
    "msg": "user.name=paul key=keys/paul_key1.pub"
}
ok: [localhost] => (item=[{u'mysql': {u'hosts': [u'%', u'127.0.0.1', u'::1', u'localhost']}, u'password': u'paul_pass', u'authorized': [u'keys/paul_key1.pub', u'keys/paul_key2.pub'], u'groups': [u'wheel'], u'name': u'paul'}, u'keys/paul_key2.pub']) => {
    "msg": "user.name=paul key=keys/paul_key2.pub"
}
ok: [localhost] => (item=[{u'mysql': {u'password': u'other-mysql-password', u'hosts': [u'utility']}, u'password': u'john_pass', u'authorized': [u'keys/john_key.pub'], u'groups': [u'wheel', u'devops'], u'name': u'john'}, u'keys/john_key.pub']) => {
    "msg": "user.name=john key=keys/john_key.pub"
}

TASK [輸出到 /tmp/subelements_output.yml 再來觀察] ***********************************************************************
changed: [localhost]
... 省略 ...

讓我們看看 /tmp/subelements_output.yml (使用 cat /tmp/subelements.yml)

- - &id001
    authorized: [keys/paul_key1.pub, keys/paul_key2.pub]
    groups: [wheel]
    mysql:
      hosts: ['%', 127.0.0.1, '::1', localhost]
    name: paul
    password: paul_pass
  - keys/paul_key1.pub
- - *id001
  - keys/paul_key2.pub
- - authorized: [keys/john_key.pub]
    groups: [wheel, devops]
    mysql:
      hosts: [utility]
      password: other-mysql-password
    name: john
    password: john_pass
  - keys/john_key.pub

可以看出來,跟我們之前所預期的幾乎一樣。 只是輸出的結果裡,使用了 &id001 來定義錨點,第二個元素使用 *id001 來引用之前定義的錨點以減少資料量。

看到這邊相信你已經了解 subelements 怎麼使用了吧。