如何从作为列表的 dict 属性匹配/搜索子字符串

发布于 2025-01-17 12:22:13 字数 4442 浏览 7 评论 0原文

场景如下:

  • 一个 playbook 调用一个角色在多个服务器中创建用户,包括 VM 规模集(其中 ansible_hostnames 无法预测) - 库存已经动态生成并且工作正常,而不是
  • users dict 变量会出现 的问题提供用户列表以及每个
  • 属性的一系列属性是一个名为 target_servers 的服务器列表 - 该变量的属性是实际问题
  • target_servers 由剧本使用来决定用户是否将该特定服务器上存在/不存在 - 它补充了 ansible 的清单
  • target_servers 可能仅包含特定目标主机的起始名称、子字符串,例如“vmss”作为“vmss*”通配符,但也包括固定主机名 server12345、server12346、等等
  • ,动态清单告诉ansible要连接到哪些服务器,但变量告诉它是否应该在该特定服务器上创建或删除用户(即服务器有不同的用户)

目标:

有一个条件来检查 target_server 列表元素内容是否与 ansible_hostname 匹配(即,如果在 target_servers 列表中找到的子字符串(来自用户字典)匹配,那么我们提供用户;另外,当然,如果列表提供了整个主机名,则它应该匹配并且还配置了用户)

这是代码:

---
- hosts: all
  become: yes
  vars:
    users:
      user1:
          is_sudo: no
          is_chrooted: yes
          auth_method: hvault
          sa_homedir: firstname1lastname1
          state: present
          target_servers:
            - vmss
            - ubuntu
      user2:
          is_sudo: no
          is_chrooted: yes
          auth_method: hvault
          sa_homedir: firstname2lastname2
          state: present
          target_servers:
            - vmss
            - ubuntu18
  tasks:
  - debug:
      msg: "{{ ansible_hostname }}"

  - debug:
      msg: "{{ item.value.target_servers }}"
    loop: "{{ lookup('dict', users|default({})) }}"

  # This is just to exemplify what I'm trying to achieve as it is not supposed to work
  - debug:
      msg: "ansible_hostname is in target_servers of {{ item.key }}"
    loop: "{{ lookup('dict', users|default({})) }}"
    when: ansible_hostname is match(item.value.target_servers)

这是显示 match 字符串测试不能应用于列表的输出(如预期的那样):

TASK [debug] ************************************************************************************************************************************************
ok: [ubuntu18] =>
  msg: ubuntu18

TASK [debug] ************************************************************************************************************************************************
ok: [ubuntu18] => (item={'key': 'user1', 'value': {'is_sudo': False, 'is_chrooted': True, 'auth_method': 'hvault', 'sa_homedir': 'firstname1lastname1', 'state': 'present', 'target_servers': ['vmss', 'ubuntu']}}) =>
  msg:
  - vmss
  - ubuntu
ok: [ubuntu18] => (item={'key': 'user2', 'value': {'is_sudo': False, 'is_chrooted': True, 'auth_method': 'hvault', 'sa_homedir': 'firstname2lastname2', 'state': 'present', 'target_servers': ['vmss', 'ubuntu18']}}) =>
  msg:
  - vmss
  - ubuntu18

TASK [debug] ************************************************************************************************************************************************
fatal: [ubuntu18]: FAILED! =>
  msg: |-
    The conditional check 'ansible_hostname is match(item.value.target_servers)' failed. The error was: Unexpected templating type error occurred on ({% if ansible_hostname is match(item.value.target_servers) %} True {% else %} False {% endif %}): unhashable type: 'list'

    
    The error appears to be in 'test-play-users-core.yml': line 32, column 5, but may
    be elsewhere in the file depending on the exact syntax problem.

    The offending line appears to be:


      - debug:
        ^ here

已经尝试研究 selectattrjson_querysubelements 但我目前缺乏对如何使它们匹配子字符串的理解在 dict 属性内这是一个清单。

在上面的示例中,通过从 is match() 更改为 in,精确的主机名可以正常工作,但这不是目标。我需要匹配这些主机名的确切主机名和子字符串。

任何有关如何实现此目的的帮助或有关替代方法的建议将不胜感激。


如果我能找到一种方法在循环遍历整个字典后针对列表(target_servers)运行它(可以嵌套循环吗?),那么这里的示例可能会起作用: https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-strings

我想我刚刚找到了我需要的东西:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/subelements_lookup.html
将尝试尽快提供更新。

更新:是的,子元素有效!这是所需的代码:

- name: test 1
  debug:
    msg: "{{ item.1 }} matches {{ ansible_hostname }}"
  with_subelements:
    - "{{ users }}"
    - target_servers
  when: >
    ansible_hostname is match(item.1)

Here's the scenario:

  • a playbook that calls a role to create users in multiple servers, including a VM Scale Set (where ansible_hostnames can't be predicted) - inventory is already being dynamically generated and works fine and not the issue
  • a users dict variable will provide the user list as well as a series of attributes for each
  • one of these attributes is a server list named target_servers - this variable's attribute is the actual issue
  • target_servers is used by the playbook to decide if the user will be present/absent on that particular server - it complements ansible's inventory
  • target_servers might include only the starting name of a particular target host, a sub-string, like "vmss" as a "vmss*" wildcard, but also fixed hostnames server12345, server12346, etc.
  • so, dynamic inventory tells ansible which servers to connect to, but the variable tells it whether the user should be created or removed from that particular servers (i.e. servers have different users)

Objective(s):

Have a conditional that checks if a target_server list element content matches the ansible_hostname (i.e. if the substring found in the target_servers list (from the users dict) matches, then we provision the user; additionally, off course, if the list provides the entire hostname, it should match and the users also be provisioned)

Here's the code:

---
- hosts: all
  become: yes
  vars:
    users:
      user1:
          is_sudo: no
          is_chrooted: yes
          auth_method: hvault
          sa_homedir: firstname1lastname1
          state: present
          target_servers:
            - vmss
            - ubuntu
      user2:
          is_sudo: no
          is_chrooted: yes
          auth_method: hvault
          sa_homedir: firstname2lastname2
          state: present
          target_servers:
            - vmss
            - ubuntu18
  tasks:
  - debug:
      msg: "{{ ansible_hostname }}"

  - debug:
      msg: "{{ item.value.target_servers }}"
    loop: "{{ lookup('dict', users|default({})) }}"

  # This is just to exemplify what I'm trying to achieve as it is not supposed to work
  - debug:
      msg: "ansible_hostname is in target_servers of {{ item.key }}"
    loop: "{{ lookup('dict', users|default({})) }}"
    when: ansible_hostname is match(item.value.target_servers)

Here's the output showing that the match string test cannot be applied to a list (as expected):

TASK [debug] ************************************************************************************************************************************************
ok: [ubuntu18] =>
  msg: ubuntu18

TASK [debug] ************************************************************************************************************************************************
ok: [ubuntu18] => (item={'key': 'user1', 'value': {'is_sudo': False, 'is_chrooted': True, 'auth_method': 'hvault', 'sa_homedir': 'firstname1lastname1', 'state': 'present', 'target_servers': ['vmss', 'ubuntu']}}) =>
  msg:
  - vmss
  - ubuntu
ok: [ubuntu18] => (item={'key': 'user2', 'value': {'is_sudo': False, 'is_chrooted': True, 'auth_method': 'hvault', 'sa_homedir': 'firstname2lastname2', 'state': 'present', 'target_servers': ['vmss', 'ubuntu18']}}) =>
  msg:
  - vmss
  - ubuntu18

TASK [debug] ************************************************************************************************************************************************
fatal: [ubuntu18]: FAILED! =>
  msg: |-
    The conditional check 'ansible_hostname is match(item.value.target_servers)' failed. The error was: Unexpected templating type error occurred on ({% if ansible_hostname is match(item.value.target_servers) %} True {% else %} False {% endif %}): unhashable type: 'list'

    
    The error appears to be in 'test-play-users-core.yml': line 32, column 5, but may
    be elsewhere in the file depending on the exact syntax problem.

    The offending line appears to be:


      - debug:
        ^ here

Already tried researching about selectattr, json_query and subelements but I currently lack the understanding on how to make them work to match a substring inside a dict attribute that is a list.

In the example above, by changing from is match() to in, exact hostnames work fine, but that is not the goal. I need to match both exact hostnames and sub-strings for these hostnames.

Any help on how to accomplish this or suggestions about alternate methods will be greatly appreciated.


The example here might work if I could find a way to run it against a list (target_servers) after having already looped through the entire dictionary (are nested loops possible?): https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-strings

I guess I've just found what I needed: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/subelements_lookup.html
Will try and provide an update soon.

Update: yes, subelements work! Here's the code needed:

- name: test 1
  debug:
    msg: "{{ item.1 }} matches {{ ansible_hostname }}"
  with_subelements:
    - "{{ users }}"
    - target_servers
  when: >
    ansible_hostname is match(item.1)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

ヅ她的身影、若隐若现 2025-01-24 12:22:13

您可以使用选择< /a> 过滤器以应用 in 测试用户的target_servers 列表中的所有元素。

这将是您的调试任务:

- debug:
    msg: "hostname is in target_servers of {{ item.key }}"
  loop: "{{ users | dict2items  }}"
  loop_control:
    label: "{{ item.key }}"
  when: >-
    item.value.target_servers 
    | select('in', inventory_hostname) 
    | length > 0

给定剧本:

- hosts: all
  gather_facts: false
  vars:
    _hostname: ubuntu18
    users:
      user1:
          target_servers:
            - vmss
            - ubuntu
      user2:
          target_servers:
            - vmss
            - ubuntu18

  tasks:
    - debug:
        msg: "hostname is in target_servers of {{ item.key }}"
      loop: "{{ users | dict2items  }}"
      loop_control:
        label: "{{ item.key }}"
      when: >-
        item.value.target_servers 
        | select('in', inventory_hostname) 
        | length > 0

这会产生:

ok: [ubuntu18] => (item=user1) => 
  msg: hostname is in target_servers of user1
ok: [ubuntu18] => (item=user2) => 
  msg: hostname is in target_servers of user2

使用 子元素 相反:

- hosts: all
  gather_facts: false
  vars:
    _hostname: ubuntu18
    users:
      user1:
          target_servers:
            - vmss
            - ubuntu
      user2:
          target_servers:
            - vmss
            - ubuntu18

  tasks:
    - debug:
        msg: "hostname is in target_servers of {{ item.0.key }}"
      loop: "{{ users | dict2items | subelements('value.target_servers')  }}"
      loop_control:
        label: "{{ item.0.key }}"
      when: item.1 in inventory_hostname

将产生:

skipping: [ubuntu18] => (item=user1) 
ok: [ubuntu18] => (item=user1) => 
  msg: hostname is in target_servers of user1
skipping: [ubuntu18] => (item=user2) 
ok: [ubuntu18] => (item=user2) => 
  msg: hostname is in target_servers of user2

You can use the select filter to apply the in test to all the elements of your users' target_servers list.

This would be your debug task:

- debug:
    msg: "hostname is in target_servers of {{ item.key }}"
  loop: "{{ users | dict2items  }}"
  loop_control:
    label: "{{ item.key }}"
  when: >-
    item.value.target_servers 
    | select('in', inventory_hostname) 
    | length > 0

Given the playbook:

- hosts: all
  gather_facts: false
  vars:
    _hostname: ubuntu18
    users:
      user1:
          target_servers:
            - vmss
            - ubuntu
      user2:
          target_servers:
            - vmss
            - ubuntu18

  tasks:
    - debug:
        msg: "hostname is in target_servers of {{ item.key }}"
      loop: "{{ users | dict2items  }}"
      loop_control:
        label: "{{ item.key }}"
      when: >-
        item.value.target_servers 
        | select('in', inventory_hostname) 
        | length > 0

This yields:

ok: [ubuntu18] => (item=user1) => 
  msg: hostname is in target_servers of user1
ok: [ubuntu18] => (item=user2) => 
  msg: hostname is in target_servers of user2

Doing it with subelements instead:

- hosts: all
  gather_facts: false
  vars:
    _hostname: ubuntu18
    users:
      user1:
          target_servers:
            - vmss
            - ubuntu
      user2:
          target_servers:
            - vmss
            - ubuntu18

  tasks:
    - debug:
        msg: "hostname is in target_servers of {{ item.0.key }}"
      loop: "{{ users | dict2items | subelements('value.target_servers')  }}"
      loop_control:
        label: "{{ item.0.key }}"
      when: item.1 in inventory_hostname

Will yield:

skipping: [ubuntu18] => (item=user1) 
ok: [ubuntu18] => (item=user1) => 
  msg: hostname is in target_servers of user1
skipping: [ubuntu18] => (item=user2) 
ok: [ubuntu18] => (item=user2) => 
  msg: hostname is in target_servers of user2
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文