如何从Ansible的register.results变量生成指定结构的字典
问题:从Proxmox KVM任务结果中筛选changed项生成指定字典结构
需要从provisioning_vm变量中筛选出changed状态为true的项,生成如下结构的vm_dict字典:
vm_dict: 80001: host_name: ansible-clone1 host_ip_address: 10.10.0.1 80002: host_name: ansible-clone2 host_ip_address: 10.10.0.2
现有尝试及问题
使用set_fact和combine过滤器的代码如下,但存在两个问题:无法同时添加host_name和host_ip_address两个值,且changed状态判断逻辑错误:
- name: Populate dict set_fact: _dict: "{{ _dict|default({}) | combine( {item.item.key: item.item.value.host_name} ) }}" loop: "{{ provisioning_vm.results }}" when: provisioning_vm.changed == true
任务结果变量结构
provisioning_vm的具体结构:
{ "provisioning_vm": { "changed": false, "msg": "All items completed", "results": [ { "ansible_loop_var": "item", "changed": false, "failed": false, "invocation": { "module_args": { "acpi": null, "agent": null, "api_host": "10.0.10.1:8006", "api_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "api_token_id": null, "api_token_secret": null, "api_user": "ansible_admin@pve", "args": null, "autostart": null, "balloon": null, "bios": null, "boot": null, "bootdisk": null, "cicustom": null, "cipassword": null, "citype": null, "ciuser": null, "clone": "arbitrary_template", "cores": null, "cpu": null, "cpulimit": null, "cpuunits": null, "delete": null, "description": null, "digest": null, "efidisk0": null, "force": null, "format": "raw", "freeze": null, "full": true, "hostpci": null, "hotplug": null, "hugepages": null, "ide": null, "ipconfig": null, "keyboard": null, "kvm": null, "localtime": null, "lock": null, "machine": null, "memory": null, "migrate_downtime": null, "migrate_speed": null, "name": "ansible-clone1.dev.test.com", "nameservers": null, "net": null, "newid": 80001, "node": "proxmox01", "numa": null, "numa_enabled": null, "onboot": null, "ostype": null, "parallel": null, "pool": null, "protection": null, "proxmox_default_behavior": "no_defaults", "reboot": null, "revert": null, "sata": null, "scsi": null, "scsihw": null, "searchdomains": null, "serial": null, "shares": null, "skiplock": null, "smbios": null, "snapname": null, "sockets": null, "sshkeys": null, "startdate": null, "startup": null, "state": "present", "storage": "CEPH-DEV", "tablet": null, "tags": null, "target": null, "tdf": null, "template": null, "timeout": 400, "update": false, "validate_certs": false, "vcpus": null, "vga": null, "virtio": null, "vmid": 987654, "watchdog": null } }, "item": { "key": 80001, "value": { "host_ip_address": "10.10.0.1", "host_name": "ansible-clone1.dev.test.com" } }, "msg": "VM with name <ansible-clone1.dev.test.com> already exists", "vmid": 80001 }, { "ansible_loop_var": "item", "changed": false, "failed": false, "invocation": { "module_args": { "acpi": null, "agent": null, "api_host": "10.0.10.1:8006", "api_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "api_token_id": null, "api_token_secret": null, "api_user": "ansible_admin@pve", "args": null, "autostart": null, "balloon": null, "bios": null, "boot": null, "bootdisk": null, "cicustom": null, "cipassword": null, "citype": null, "ciuser": null, "clone": "arbitrary_template", "cores": null, "cpu": null, "cpulimit": null, "cpuunits": null, "delete": null, "description": null, "digest": null, "efidisk0": null, "force": null, "format": "raw", "freeze": null, "full": true, "hostpci": null, "hotplug": null, "hugepages": null, "ide": null, "ipconfig": null, "keyboard": null, "kvm": null, "localtime": null, "lock": null, "machine": null, "memory": null, "migrate_downtime": null, "migrate_speed": null, "name": "ansible-clone2.dev.test.com", "nameservers": null, "net": null, "newid": 80002, "node": "proxmox01", "numa": null, "numa_enabled": null, "onboot": null, "ostype": null, "parallel": null, "pool": null, "protection": null, "proxmox_default_behavior": "no_defaults", "reboot": null, "revert": null, "sata": null, "scsi": null, "scsihw": null, "searchdomains": null, "serial": null, "shares": null, "skiplock": null, "smbios": null, "snapname": null, "sockets": null, "sshkeys": null, "startdate": null, "startup": null, "state": "present", "storage": "CEPH-DEV", "tablet": null, "tags": null, "target": null, "tdf": null, "template": null, "timeout": 400, "update": false, "validate_certs": false, "vcpus": null, "vga": null, "virtio": null, "vmid": 987654, "watchdog": null } }, "item": { "key": 80002, "value": { "host_ip_address": "10.10.0.2", "host_name": "ansible-clone2.dev.test.com" } }, "msg": "VM with name <ansible-clone2.dev.test.com> already exists", "vmid": 80002 }, { "ansible_loop_var": "item", "changed": false, "failed": false, "invocation": { "module_args": { "acpi": null, "agent": null, "api_host": "10.0.10.1:8006", "api_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "api_token_id": null, "api_token_secret": null, "api_user": "ansible_admin@pve", "args": null, "autostart": null, "balloon": null, "bios": null, "boot": null, "bootdisk": null, "cicustom": null, "cipassword": null, "citype": null, "ciuser": null, "clone": "arbitrary_template", "cores": null, "cpu": null, "cpulimit": null, "cpuunits": null, "delete": null, "description": null, "digest": null, "efidisk0": null, "force": null, "format": "raw", "freeze": null, "full": true, "hostpci": null, "hotplug": null, "hugepages": null, "ide": null, "ipconfig": null, "keyboard": null, "kvm": null, "localtime": null, "lock": null, "machine": null, "memory": null, "migrate_downtime": null, "migrate_speed": null, "name": "ansible-clone4.dev.test.com", "nameservers": null, "net": null, "newid": 80004, "node": "proxmox01", "numa": null, "numa_enabled": null, "onboot": null, "ostype": null, "parallel": null, "pool": null, "protection": null, "proxmox_default_behavior": "no_defaults", "reboot": null, "revert": null, "sata": null, "scsi": null, "scsihw": null, "searchdomains": null, "serial": null, "shares": null, "skiplock": null, "smbios": null, "snapname": null, "sockets": null, "sshkeys": null, "startdate": null, "startup": null, "state": "present", "storage": "CEPH-DEV", "tablet": null, "tags": null, "target": null, "tdf": null, "template": null, "timeout": 400, "update": false, "validate_certs": false, "vcpus": null, "vga": null, "virtio": null, "vmid": 987654, "watchdog": null } }, "item": { "key": 80004, "value": { "host_ip_address": "10.10.10.4", "host_name": "ansible-clone4.dev.test.com" } }, "msg": "VM with name <ansible-clone4.dev.test.com> already exists", "vmid": 80004 } ], "skipped": false } }
解决方案
问题分析
- changed状态判断错误:原代码判断的是整个循环任务的
provisioning_vm.changed,但需要判断每个子任务结果的item.changed - 字典值缺失:原代码只传入了
host_name,需要将包含host_name和host_ip_address的整个item.item.value(或处理后的结构)传入combine
实现方式1:循环生成字典
适合需要对字段做额外处理(比如去掉域名)的场景:
- name: 生成changed状态的VM字典 set_fact: vm_dict: "{{ vm_dict | default({}) | combine( { item.item.key: { 'host_name': item.item.value.host_name.split('.')[0], 'host_ip_address': item.item.value.host_ip_address } }) }}" loop: "{{ provisioning_vm.results }}" when: item.changed
实现方式2:过滤器链式生成(更高效)
利用Ansible过滤器一次性处理,无需循环:
- name: 一次性生成changed状态的VM字典 set_fact: vm_dict: >- {% set changed_items = provisioning_vm.results | selectattr('changed', 'equalto', true) | map(attribute='item') %} {% set processed = [] %} {% for item in changed_items %} {% do processed.append({ 'key': item.key, 'value': { 'host_name': item.value.host_name.split('.')[0], 'host_ip_address': item.value.host_ip_address } }) %} {% endfor %} {{ processed | items2dict }}
如果不需要去掉域名,直接使用原host_name,可以简化为:
- name: 一次性生成changed状态的VM字典(保留完整域名) set_fact: vm_dict: "{{ provisioning_vm.results | selectattr('changed', 'true') | map(attribute='item') | items2dict }}"
内容的提问来源于stack exchange,提问作者Adam Palka
相关产品推荐
相关产品推荐

