在Ansible中,如何查询hostvars以根据不同键的值从列表项中获取键的特定值?

In Ansible, how to query hostvars to get a specific value of a key from a list item based on the value of a different key?

编辑更新:

我找到了一种方法来实现我想要做的事情,使用 index_of 插件。下面的代码输出我需要的。

---
- hosts: CASPOSR1BDAT003
  connection: local
  gather_facts: no
  become: false
  tasks:
    - ansible.builtin.set_fact:
        mac_address: "{{ hostvars[inventory_hostname]['interfaces'][int_idx|int]['mac_address'] }}"
      vars:
        int_name: 'PCI1.1'
        int_idx: "{{ lookup('ansible.utils.index_of', hostvars[inventory_hostname]['interfaces'], 'eq', int_name, 'name') }}"
    - debug:
        var: mac_address

输出:

PLAY [CASPOSR1BDAT003] ***********************************************************************************************************************************************************************************************

TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************
ok: [CASPOSR1BDAT003]

TASK [debug] *********************************************************************************************************************************************************************************************************
ok: [CASPOSR1BDAT003] => 
  mac_address: 20:67:7C:00:36:A0

我想做什么:

我试过的:

  1. 将 hostvars 转换为 JSON 并使用 json_query:这没有用,并且查看了 GitHub 上的一些问题,hostvars 不是“普通”字典.无论如何,我已经记录了几个问题 (https://github.com/ansible/ansible/issues/76289 and https://github.com/ansible-collections/community.general/issues/3706)。
  2. 使用序列循环和条件“何时”获取值 - 这种方法在使用 debug 模块时有效,但仍然不只是返回值

什么有效: 我尝试了以下方法,它按预期输出 mac_address 变量。找到列表的长度,然后条件匹配名称。我确实收到了关于使用 jinja2 模板定界符的警告,但这不是这个问题的目标。

---
- hosts: CASPOSR1BDAT003
  connection: local
  gather_facts: no
  become: false
  tasks:
    - debug:
        var: hostvars[inventory_hostname]['interfaces'][{{ item }}]['mac_address']
      with_sequence: start=0 end="{{ end_at }}"
      vars:
        - end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
      when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"

结果是:

TASK [debug] *************************************************************************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found:
hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
skipping: [CASPOSR1BDAT003] => (item=0) 
skipping: [CASPOSR1BDAT003] => (item=1) 
skipping: [CASPOSR1BDAT003] => (item=2) 
skipping: [CASPOSR1BDAT003] => (item=3) 
skipping: [CASPOSR1BDAT003] => (item=4) 
ok: [CASPOSR1BDAT003] => (item=5) => 
  ansible_loop_var: item
  hostvars[inventory_hostname]['interfaces'][5]['mac_address']: 20:67:7C:00:36:A0
  item: '5'
skipping: [CASPOSR1BDAT003] => (item=6) 
skipping: [CASPOSR1BDAT003] => (item=7) 
skipping: [CASPOSR1BDAT003] => (item=8) 
skipping: [CASPOSR1BDAT003] => (item=9) 

我正在尝试使用 set_fact 来存储这个 mac_address 变量,因为我需要以几种不同的方式使用它。但是,我无法在此(或任何其他 hostvars 数据,似乎)上使用 set_fact。例如,以下内容:

---
- hosts: CASPOSR1BDAT003
  connection: local
  gather_facts: no
  become: false
  tasks:
    - ansible.builtin.set_fact:
        interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item]['mac_address'] }}"
      with_sequence: start=0 end="{{ end_at }}"
      vars:
        - end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
      when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
    - debug:
        var: interfaces

结果:

fatal: [CASPOSR1BDAT003]: FAILED! => 
  msg: |-
    The task includes an option with an undefined variable. The error was: 'list object' has no attribute '5'
  
    The error appears to be in '/Users/kivlint/Documents/GitHub/vmware-automation/ansible/prepare-pxe.yml': line 19, column 7, but may
    be elsewhere in the file depending on the exact syntax problem.
  
    The offending line appears to be:
  
        #   when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
        - ansible.builtin.set_fact:
          ^ here

如果我在其中硬编码数字 5,它工作正常:

TASK [ansible.builtin.set_fact] ******************************************************************************************************************
ok: [CASPOSR1BDAT003]

TASK [debug] *************************************************************************************************************************************
ok: [CASPOSR1BDAT003] => 
  interfaces: 20:67:7C:00:36:A0

如果我使用“5”作为任务的变量,它也有效。

---
- hosts: CASPOSR1BDAT003
  connection: local
  gather_facts: no
  become: false
  tasks:
    - ansible.builtin.set_fact:
        interfaces: "{{ hostvars[inventory_hostname]['interfaces'][int_index]['mac_address'] }}"
      vars:
        - int_index: 5

所以我想知道,这是 set_fact 如何与循环一起工作或不工作的“bug/feature”吗(意思是,同一个循环与 debug 一起工作得很好? 或者我是否需要重新考虑该方法并考虑尝试使用 set_fact 来设置一个带有列表索引的变量(例如上例中的 5)?或者其他什么?

set_fact 使用循环,但不是您期望的方式。

此示例从字典列表构造带循环的列表:

- set_fact:
    foo: '{{ foo|d([]) + [item.value] }}'
  loop:
    - value: 1
    - value: 2

基本上,set_fact 的每次执行都会创建一个事实。您可以在 set_fact 的 jinja 表达式中引用相同的事实,但您不能指望它会自动构建列表或类似的东西。

[5](列表的第 6 项)和 ['5'](名为“5”的键)之间存在混淆, 您在错误中看到:错误是:'list object' 没有 属性 '5'.

使用模块调试你没有错误,因为 [{{item}}] 被 [5] 代替而不是 ['5']。它与 set_fact 不同。

这是你必须使用过滤器 int 来澄清情况的原因。

  - ansible.builtin.set_fact:
        interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item|int]['mac_address'] }}"
      with_sequence: start=0 end="{{ end_at }}"
      vars:
        end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
      when: hostvars[inventory_hostname]['interfaces'][item|int]['name'] == "PCI1.1"

所以我建议你使用 loop 而不是 with_sequence:

- ansible.builtin.set_fact:
    interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item]['mac_address'] }}"
  loop: "{{ range(0, end_at|int, 1)|list }}"
  vars:
    end_at: "{{ hostvars[inventory_hostname]['interfaces'] | length }}"
  when: hostvars[inventory_hostname]['interfaces'][item]['name'] == "PCI1.1"

您的代码中发生了很多事情,实现您想要的结果比实现它更简单。

首先,不要使用hostvars[inventory_hostname];普通变量是属于当前主机的变量,通过 hostvars 会引入一些令人兴奋的出错机会。 hostvars 用于访问属于 other 主机的变量。

其次,使用 Jinja 的内置过滤功能可以避免担心所需项目的索引。

- hosts: CASPOSR1BDAT003
  connection: local
  gather_facts: no
  become: false
  vars:
    int_name: PCI1.1
    mac_address: "{{ interfaces | selectattr('name', 'eq', int_name) | map(attribute='mac_address') | first }}"
  tasks:
    - debug:
        var: mac_address