【问题标题】: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?在 Ansible 中,如何查询 hostvars 以根据不同键的值从列表项中获取键的特定值?
【发布时间】:2021-12-25 09:46:25
【问题描述】:

编辑更新:

我找到了一种使用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

我想做的事:

  • 使用 Netbox 动态库存插件(这很有效,可以带回我需要的所有信息)
  • 查询特定主机的主机变量,并获取名为 PCI1.1 的特定接口的 MAC 地址值

我尝试过的:

  1. 将 hostvars 转换为 JSON 并使用 json_query:这不起作用,并且在 GitHub 上查看了一些问题后,hostvars 不是“普通”字典。无论如何,我已经记录了几个问题(https://github.com/ansible/ansible/issues/76289https://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 变量,因为我需要以几种不同的方式使用它。但是,我无法在此(或任何其他主机变量数据,似乎)上使用 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' 作为任务的 var,它也可以工作。

---
- 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 如何与循环一起使用或不使用循环的“错误/功能”(意思是,相同的循环与debug 一起工作正常吗?还是我需要重新考虑方法并考虑尝试使用set_fact 设置具有列表索引的变量(例如上例中的5)?还是其他?

【问题讨论】:

    标签: ansible ansible-facts


    【解决方案1】:

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

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

    其次,使用 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
    

    【讨论】:

      【解决方案2】:

      [5](列表的第 6 项)和 ['5'](名为“5”的键)之间存在混淆, 您在错误中看到:错误是:'list object' has no attribute '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"
      

      【讨论】:

      • 谢谢!这当然有效,并回答了我最初遇到错误的原因。然而,flowerysong (stackoverflow.com/a/69958617/10556530) 的答案既解决了这个问题,又改进了代码,因此接受了它作为最佳答案。
      【解决方案3】:

      set_fact 与循环一起工作,但不是以您期望的方式。

      这个例子从字典列表中构造带有循环的列表:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-07
        • 2017-09-03
        • 2020-06-26
        • 2021-10-05
        • 2017-10-23
        相关资源
        最近更新 更多