如何在 Debian 和 FreeBSD 中的 ansible 中根据给定的 ip 地址选择网络接口?

如何在 Debian 和 FreeBSD 中的 ansible 中根据给定的 ip 地址选择网络接口?

我正在寻找一个表达式来获取 Linux 和 FreeBSD 中分配给该 iface 的 ip 地址的接口名称。

这个问题基于这个答案:https://serverfault.com/a/948288/416946

在 Debian 上,此 jinja2 表达式将返回接口对象(来自 ansible facts)given_ip

iface_for_ip: >-
  {{ ansible_facts
  | dict2items
  | selectattr('value.ipv4', 'defined')
  | selectattr('value.ipv4.address', 'equalto', given_ip)
  | first  }}

然而这在 FreeBSD 上不起作用,因为该ipv4结构是一个数组,而不是一个对象。

如果你只运行这个代码片段:

iface_for_ip: >-
  {{ ansible_facts
  | dict2items
  | selectattr('value.ipv4', 'defined') }}

你将获得如下输出:

在 Debian 上
  - key: eth0
    value:
      active: true
      device: eth0
      ipv4:
        address: 10.8.20.206
        broadcast: 10.8.20.255
        netmask: 255.255.255.0
        network: 10.8.20.0
      ipv6:
      - address: fe80::84ee:35ff:fed4:a23c
        prefix: '64'
        scope: link
      macaddress: 00:ee:35:00:00:00
      mtu: 1500
      promisc: false
      speed: 10000
      type: ether
在 FreeBSD 上
  - key: epair0b
    value:
      device: epair0b
      flags:
      - UP
      - BROADCAST
      - RUNNING
      - SIMPLEX
      - MULTICAST
      ipv4:
      - address: 10.8.20.207
        broadcast: 10.8.20.255
        netmask: 255.255.255.0
        network: 10.8.20.0
      ipv6: []
      macaddress: 00:ee:23:00:00:00
      media: Ethernet
      media_options:
      - full-duplex
      media_select: 10Gbase-T
      media_type: 10Gbase-T
      metric: '0'
      mtu: '1500'
      options:
      - PERFORMNUD
      status: active
      type: ether

如何使用 jinja2 ansible 表达式来获取跨平台仅给出 ip 地址的接口? json_query在这里可能会有用,但是方法我不清楚。

答案1

收集的数据存在差异设置在 Debian 和 FreeBSD 上。


在 Ubuntu(Debian 衍生产品)中,属性IPv4是一本字典。次要地址存储在列表中ipv4_secondaries。第一步是创建设备和 ipv4 地址列表。例如

    - debug:
        var: ansible_facts.distribution
    - set_fact:
        ifc_list: "{{ ansible_facts|
                      dict2items|
                      json_query(query)|
                      selectattr('ipv4')|list }}"
      vars:
        query: "[?value.type == 'ether'].{device: value.device,
                                          ipv4: value.ipv4.address}"
    - debug:
        var: ifc_list

给予(节略)

  ansible_facts.distribution: Ubuntu

  ifc_list:
  - device: eth0
    ipv4: 10.1.0.27

然后“选择给定 IP 地址的网络接口”

    - set_fact:
        iface_for_ip: "{{ ifc_list|
                          selectattr('ipv4', 'eq', ip_address)|
                          map(attribute='device')|list }}"
      vars:
        ip_address: "10.1.0.27"
    - debug:
        var: iface_for_ip

给予(节略)

  iface_for_ip:
  - eth0

在 FreeBSD 中,属性IPv4是一个列表。创建设备和 ipv4 列表

    - debug:
        var: ansible_facts.distribution
    - set_fact:
        ifc_list: "{{ ansible_facts|
                      dict2items|
                      json_query(query)|
                      selectattr('ipv4')|list }}"
      vars:
        query: "[?value.type == 'ether'].{device: value.device,
                                          ipv4: value.ipv4[].address}"
    - debug:
        var: ifc_list

给予(节略)

  ansible_facts.distribution: FreeBSD

  ifc_list:
  - device: wlan0
    ipv4:
    - 10.1.0.51

然后“选择给定 IP 地址的网络接口”

    - set_fact:
        iface_for_ip: "{{ iface_for_ip|default([]) + [item.device] }}"
      loop: "{{ ifc_list }}"
      when: ip_address in item.ipv4
      vars:
        ip_address: "10.1.0.51"
    - debug:
        var: iface_for_ip

给予(节略)

  iface_for_ip:
  - wlan0

从 Ansible 2.8 开始,你可以使用测试包含。下面的任务给出了相同的结果

    - set_fact:
        iface_for_ip: "{{ ifc_list|
                          selectattr('ipv4', 'contains', ip_address)|
                          map(attribute='device')|
                          unique }}"
      vars:
        ip_address: "10.1.0.51"

问:如何删除最后的 set_fact + 循环以便它可以纯粹在 vars 文件中定义?

答:属性IPv4是一个列表。要使用选择属性, 代替环形,你需要一个测试 包含(序列,值)。Ansible 2.7 及以下版本中没有这样的测试。只有输入(值,序列)可以使用参数反转顺序的测试。如果您没有 Ansible 2.8 或更高版本,则必须编写自己的测试。例如

shell> cat test_plugins/contains.py 

def contains(l, value):
    return value in l


class TestModule:
    """Main test class from Ansible."""

    def tests(self):
        """Add these tests to the list of tests available to Ansible."""
        return {
            'contains': contains
        }

然后设置事实给出相同的结果。该表达式也可以用于瓦尔斯

    - set_fact:
        iface_for_ip: "{{ ifc_list|
                          selectattr('ipv4', 'contains', ip_address)|
                          map(attribute='device')|list }}"

答案2

受到其他答案的启发那里链接的要点

vars:
    freebsd_query: "[*].{device: device, active: active, ipv6: ipv6, ipv4: ipv4[? address == '{{ ip_find_iface }}']}[?ipv4])" # string must be in ' # sorry, only partial interface info, did not find out how to return all info directly
    linux_query: "[?ipv4.address == '{{ ip_find_iface }}']" # string must be in '
    ipv6_query: "[*].{device: device, active: active, ipv4: ipv4, ipv6: ipv6[? address == '{{ ip_find_iface }}']}[?ipv6]" # string must be in ' # sorry, only partial info, did not find how to return the full one
    ip_query: "{{ ip_find_iface | ipv6 | ternary(
        ipv6_query,
        ansible_facts[ansible_facts.interfaces | first].ipv4 is mapping | ternary(linux_query, freebsd_query)
    ) }}.device"
    all_interfaces: "{{ ansible_facts.interfaces | map('extract', ansible_facts) }}"
    iface_for_ip: "{{ all_interfaces | json_query(ip_query) }}"

ip_query首先检查 ip 是否为 ipv6 格式。如果不是,则检查是否{anyNetwork}.ipv4为字典。json_query()根据此选择查询。

(可能有错别字,因为我无法(简单地)测试所有内容。已经更正了大约 5 次……)
(如何发展:https://gist.github.com/Ramblurr/5d8324e0154ea6be52407618222fcaf7

相关内容