【问题标题】:Ansible inline jinja templating out to a listAnsible 内联 jinja 模板化到列表
【发布时间】:2020-04-05 08:31:48
【问题描述】:

我不明白为什么这行不通。 我正在使用 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

一个示例变量是:

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 }}"

var 调试:

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 它是一个列表。 所以这绝对是一个列表。

【问题讨论】:

  • 在 set_fact 任务之后添加“- name: Print a variable \n debug: \n var: additional_disks”并验证变量是否定义正确。
  • 看看,我添加了var的输出
  • 你看到这个符号了吗:|-不知道这是从哪里来的
  • 好吧,这可能是一个愚蠢的解决方法,但是您是否尝试使用 {{ vm_disks|replace ('|-','') }} 将 '|-' 替换为无 '' ?跨度>
  • 等等,对于多行来说,只需 '>' 就足够了,对吧?为什么在 '>-' 中使用连字符?另一个愚蠢(也许不是)的解决方法是将 set_fact 中的所有 Jinja 代码放在一行中,而不是使用 >-

标签: ansible jinja2


【解决方案1】:

正如 Uttam 正确指出但没有提供答案的那样,问题是您的 set_fact: 产生了一个 字符串disks 必须是 listdict ,从the fine manual(和the code)可以看出

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

第二个是永远不要在jinja中构建丰富的数据结构使用文本:它对这些数据结构有强大的支持,以及美妙的| to_json|from_json过滤器确保输出是合法的 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 }}

【讨论】:

  • 我想我正在做你所说的,但它给出了完全相同的错误。我将在我的问题和调试中添加新代码。
  • 您没有按照我说的做,而是出于某种原因将输出的小胡子字符用双引号括起来,这导致结果字符串为"[,这不是 JSON——或者更确切地说它是 JSON 字符串 而不是 JSON 列表
  • 谢谢老兄,我终于明白你的意思了,我只是看错地方了。查看所有 jinja 代码本身。但它是“{{result}}”部分。奇怪的是,我的 IDE 告诉我要加上引号,否则会出错(pycharm)
【解决方案2】:

最终的工作代码,非常感谢@mdaniel

- 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: >-
      {%- 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([]) -%}
        {%- if (disk.size_gb is defined) -%}
          {%- set d = {"size_gb": disk.size_gb} -%}
          {%- set _ = d.update({"datastore": disk.datastore_name}) -%}
          {%- set _ = results.append(d) -%}
        {%- endif -%}
        {%- if (disk.size_mb is defined) -%}
          {%- set d = {"size_mb": disk.size_mb} -%}
          {%- set _ = d.update({"datastore": disk.datastore_name}) -%}
          {%- set _ = results.append(d) -%}
        {%- endif -%}
        {%- if (disk.size_kb is defined) -%}
          {%- set d = {"size_kb": disk.size_kb} -%}
          {%- set _ = d.update({"datastore": disk.datastore_name}) -%}
          {%- set _ = results.append(d) -%}
        {%- endif -%}
      {%- endfor -%}
      {{ results }}

- 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

并像这样设置你的变量,你需要的任何组合:

additional_disks:
  - size_gb: "120"
    datastore_name: "VSAN_Datastore"
  - size_mb: "10240"
    datastore_name: "VSAN_Datastore"
  - size_kb: "10240000"
    datastore_name: "VSAN_Datastore"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多