首页 > 解决方案 > Ansible 内联 jinja 模板化到列表

问题描述

我不明白为什么这行不通。我正在使用 jinja 动态生成要传递给 vmware_guest 模块的列表,因此我可以在主机或组变量中决定是否要添加其他磁盘。我正在使用 vmware_disk_info 模块来查找模板磁盘的大小,然后添加我将在组 var 中定义的任何其他磁盘。在我看来,输出的列表不是列表????

---
- name: "Get facts for named template"
  vmware_guest_disk_info:
    hostname: "{{ vcenter_server }}"
    username: "{{ vcenter_user }}"
    password: "{{ vcenter_pass }}"
    validate_certs: False
    datacenter: "{{ datacenter_name }}"
    name: "{{ template_name }}"
  register: template_disk
  delegate_to: localhost

- name: "Define new disk structure"
  set_fact:
    vm_disks: >-
      [{% for disk in (template_disk.guest_disk_info|dictsort) %}{
        'size_kb': {{ disk[1].capacity_in_kb }},
        'datastore': {{ datastore_name }}},
      {% endfor %}
      {% for disk in additional_disks|default([]) %}{
        {% if disk.size_gb is defined %}'size_gb': {{ disk.size_gb }},{% endif %}
        {% if disk.size_mb is defined %}'size_mb': {{ disk.size_mb }},{% endif %}
        {% if disk.size_kb is defined %}'size_kb': {{ disk.size_kb }},{% endif %}
        'datastore': {{ datastore_name }}},
      {% endfor %}]
  delegate_to: localhost

- name: Clone the template
  vmware_guest:
    hostname: "{{ vcenter_server }}"
    username: "{{ vcenter_user }}"
    password: "{{ vcenter_pass }}"
    validate_certs: False
    name: "{{ inventory_hostname }}"
    template: "{{ template_name }}"
    datacenter: "{{ datacenter_name }}"
    folder: "/{{ datacenter_name }}/vm/{{ folder_name }}"
    cluster: "{{ cluster_name }}"
    datastore: "{{ datastore_name }}"
    resource_pool: "{{ resource_pool_name }}"
    disk: "{{ vm_disks }}"
    hardware:
      memory_gb: "{{ mem_size_gb }}"
      num_cpu: "{{ cpu_size }}"
    networks:
    - name: "{{ network_name }}"
      ip: "{{ ansible_host }}"
      netmask: "{{ network_mask }}"
      gateway: "{{ network_gw }}"
      type: static
    customization:
      hostname: "{{ inventory_hostname }}"
      domain: "{{ domain_name }}"
      dns_suffix:
        - "{{ domain_name }}"
      dns_servers: "{{ network_dns }}"
    state: poweredon
    wait_for_ip_address: yes
  delegate_to: localhost

一个示例 var 将是:

additional_disks:
  - size_gb: "120"
    datastore: "VSAN_Datastore"

错误出来:

fatal: [docker02 -> localhost]: FAILED! => changed=false
  module_stderr: |-
    Traceback (most recent call last):
      File "/home/piwi/.ansible/tmp/ansible-tmp-1586075176.0007603-201082838827202/AnsiballZ_vmware_guest.py", line 102, in <module>
        _ansiballz_main()
      File "/home/piwi/.ansible/tmp/ansible-tmp-1586075176.0007603-201082838827202/AnsiballZ_vmware_guest.py", line 94, in _ansiballz_main
        invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
      File "/home/piwi/.ansible/tmp/ansible-tmp-1586075176.0007603-201082838827202/AnsiballZ_vmware_guest.py", line 40, in invoke_module
        runpy.run_module(mod_name='ansible.modules.cloud.vmware.vmware_guest', init_globals=None, run_name='__main__', alter_sys=True)
      File "/usr/lib/python3.6/runpy.py", line 205, in run_module
        return _run_module_code(code, init_globals, run_name, mod_spec)
      File "/usr/lib/python3.6/runpy.py", line 96, in _run_module_code
        mod_name, mod_spec, pkg_name, script_name)
      File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2834, in <module>
      File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2823, in main
      File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2342, in deploy_vm
      File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2005, in configure_disks
    AttributeError: 'str' object has no attribute 'get'
  module_stdout: ''
  msg: |-
    MODULE FAILURE
    See stdout/stderr for the exact error
  rc: 1
The full traceback is:
Traceback (most recent call last):
  File "/home/piwi/.ansible/tmp/ansible-tmp-1586075175.423514-45894902001082/AnsiballZ_vmware_guest.py", line 102, in <module>
    _ansiballz_main()
  File "/home/piwi/.ansible/tmp/ansible-tmp-1586075175.423514-45894902001082/AnsiballZ_vmware_guest.py", line 94, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/home/piwi/.ansible/tmp/ansible-tmp-1586075175.423514-45894902001082/AnsiballZ_vmware_guest.py", line 40, in invoke_module
    runpy.run_module(mod_name='ansible.modules.cloud.vmware.vmware_guest', init_globals=None, run_name='__main__', alter_sys=True)
  File "/usr/lib/python3.6/runpy.py", line 205, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2834, in <module>
  File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2823, in main
  File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2342, in deploy_vm
  File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2005, in configure_disks
AttributeError: 'str' object has no attribute 'get'

示例变量输出:

ok: [gluster01] =>
  vm_disks: |-
    [{
      'size_kb': 125829120,
      'datastore': VSAN_Datastore},
     {
      'size_gb': 120,      'datastore': VSAN_Datastore},
    ]
ok: [gluster02] =>
  vm_disks: |-
    [{
      'size_kb': 125829120,
      'datastore': VSAN_Datastore},
     {
      'size_gb': 120,      'datastore': VSAN_Datastore},
    ]
ok: [docker01] =>
  vm_disks: |-
    [{
      'size_kb': 125829120,
      'datastore': VSAN_Datastore},
     ]

基于以下建议的新代码:但是同样的错误:

- name: "Define new disk structure"
  set_fact:
    vm_disks: >-
      {%- set results = [] -%}
      {%- for osdisk in ( template_disk.guest_disk_info | dictsort ) -%}
      {%- set od = { "size_kb": osdisk[1].capacity_in_kb } -%}
      {%- set _ = od.update({ "datastore": osdisk[1].backing_datastore }) -%}
      {%- set _ = results.append(od) -%}
      {%- endfor -%}
      {%- for disk in additional_disks|default([]) -%}
      {%- set d = {"size_gb": disk.size_gb} if (disk.size_gb is defined) else {} -%}
      {%- set _ = d.update({"datastore": disk.datastore_name}) -%}
      {%- set _ = results.append(d) -%}
      {%- endfor -%}
      "{{ results }}"

变量调试:

TASK [vm_clone : Debugging var] *******************************************************************************************************************
ok: [gluster01] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}, {''size_gb'': ''120'', ''datastore'': ''VSAN_Datastore''}]"'
ok: [gluster02] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}, {''size_gb'': ''120'', ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker01] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker02] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker03] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker04] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker05] =>
  vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'

这对我来说看起来像一个列表,所以为了验证这一点,我将结果传送到 to_json 过滤器,它错误地说,它不是一个 str 它是一个列表。所以它绝对是一个列表。

标签: ansiblejinja2

解决方案


正如 Uttam 正确指出的那样,但没有提供答案,问题是您set_fact:产生了一个字符串,但disks必须是listof ,正如从精美手册(和代码dict中可以看到的那样

有两个原因:第一个是 ansible 仅将看起来 JSON 的字符串自动强制转换为实际的 pythonlistdict结构,但是您使用了带有单引号字符串文字和尾随逗号的python语法,这两者都是非法的 JSON

第二个是永远不应该使用文本在 jinja 中构建丰富的数据结构:它对这些数据结构有强大的支持,以及确保输出是合法的 JSON 和正确转义的字符的美妙| to_json和过滤器|from_json

但我知道这是很多话,所以我相信你可以做的最小的改变是让你的情况正常工作是停止使用单引号并保护尾随逗号:

- name: "Define new disk structure"
  set_fact:
    vm_disks: >-
      [
      {% for disk in (template_disk.guest_disk_info|dictsort) %}
      {{ "" if loop.first else "," }}
      {
        "size_kb": {{ disk[1].capacity_in_kb }},
        "datastore": "{{ datastore_name }}"
      }
      {% endfor %}
      {% for disk in additional_disks|default([]) %}
      {{ "," if template_disk.guest_disk_info else "" }}
      {
        {% if disk.size_gb is defined %}"size_gb": {{ disk.size_gb }},{% endif %}
        {% if disk.size_mb is defined %}"size_mb": {{ disk.size_mb }},{% endif %}
        {% if disk.size_kb is defined %}"size_kb": {{ disk.size_kb }},{% endif %}
        "datastore": "{{ datastore_name }}"
        }
      {% endfor %}
      ]

正确的代码类似于:

set_fact:
  vm_disks: >-
     {%- set results = [] -%}
     {%- for disk in additional_disks|default([]) -%}
     {%-   set d = {"datastore": datastore_name} -%}
     {%-   set _ = d.update({"size_gb": disk.size_gb} if (disk.size_gb is defined) else {}) -%}
     {%-   set _ = results.append(d) -%}
     {%- endfor -%}
     {{ results }}

推荐阅读