首页 > 解决方案 > 使用多个子元素最有效的方法是什么?

问题描述

我正在使用 python 进行用户管理。我有一个“user_groups”映射数组,其中每个元素都包含一个具有映射名称的键,值是一个主机数组,应该在其中配置组。

user_groups:
  robots: "test_server1,test_server2"
  developers: "test_server3,test_server4"
  tests: "test_server5"

我还有一个user_names数组,其中每个元素都包含一个地图,其中包含详细信息,例如人的用户名、该人应该关联的组以及用户应该关联的任何其他主机。

user_names:
  - username: mohamed
    group:
      - robots
      - tests
    hosts:
      user:
        - test_server4

到目前为止,我们的 ansible playbook 一直在所有主机上运行,​​并且只有当用户通过组或由每个用户定义的 hosts.user 与主机关联时,才将用户添加到主机。子元素循环有助于实现这一点。举个例子:

- name: If user is not associated with this host via groups or item.hosts.user, then ensure user is absent from host and remove user's home directory.
  become: true
  user:
    state: present
    name: "{{ item.0.username }}"
  with_subelements:
    - "{{ user_names }}"
    - group
    - flags:
      skip_missing: true
  when: (item.0.hosts is defined and item.0.hosts.user is defined and inventory_hostname in item.0.hosts.user) or (inventory_hostname in user_groups.{{ item.1 }} ))

这一直工作正常,但现在我需要确保用户在与其不关联的所有主机上都不存在。这里的主要用例是用户经历了 groups/user.hosts 更改,但它们仍将存在于不再关联的服务器上。

如果我尝试上面发布的相同代码块,但反转when条件并更改stateabsent,由于循环的性质,它不起作用;在特定主机上运行任务时,我们会循环遍历组中列出的每个服务器。并非每次迭代都会匹配inventory_hostname,因此即使关联将在以后的迭代中匹配,ansible 也会先发制人地删除用户。

我想我试图找出只有在循环的所有迭代都匹配条件时才能运行任务。或者也许有一种更优雅的方式来解决问题。

预期结果:

将用户映射到两个组。运行 ansible 以在组中包含的主机上配置用户。

从用户中删除一个组。运行 ansible 并期望将用户从不再关联的主机中删除。

我已经尝试subelements使用productjinja 过滤器进行嵌套,unions但似乎无法弄清楚这一点。

谢谢你的帮助。

标签: pythonansiblejinja2

解决方案


阅读您的评论,我更了解您要做什么。这是一种不同的方法:构建用户应该存在的主机的统一列表。

让我们从这个示例数据开始(这次我使用了两个现有变量的字典):

---
- hosts: all
  gather_facts: false
  vars:
    user_groups:
      robots: [host0, host2]
      developers: [host0]
      tests: [host2]

    user_names:
      bob:
        group:
          - robots
          - tests
        hosts:
          - host0
      alice:
        group:
          - developers
        hosts:
          - host1

现在,对于每个用户,我们将在其上定义一个统一的主机列表:

---
    # Just set a default value for user_hosts to avoid a bunch of
    # calls to the |default filter in the following expression.
    - set_fact:
        user_hosts: {}

    - set_fact:
        user_hosts: >-
          {{ user_hosts|combine({
          item.key:
          (
          item.value.hosts|default([])
          + user_groups|json_query('[{}][]'.format(','.join(item.value.group)))
          )|unique
          }) }}
      loop: "{{ user_names|dict2items }}"

    - debug:
        var: user_hosts

这将产生:

ok: [host0] => {
    "user_hosts": {
        "alice": [
            "host1", 
            "host0"
        ], 
        "bob": [
            "host0", 
            "host1", 
            "host2"
        ]
    }
}

alice被定义在,host1因为它在 . 中显式声明user_nameshost0因为她是该组的成员,所以对她进行了定义developers

bob在此示例中最终在所有三个主机上定义:host0因为这是在 中显式声明的user_nameshost1并且host2因为他在robotsandtests组中的成员身份。

一旦你有了这个列表,创建和删除用户就很简单了:

- name: create users
  debug:
    msg: "create user {{ item.key }}"
  when: inventory_hostname in item.value
  loop: "{{ user_hosts|dict2items }}"
  loop_control:
    label: "{{ item.key }}"

- name: delete users
  debug:
    msg: "delete user {{ item.key }}"
  when: inventory_hostname not in item.value
  loop: "{{ user_hosts|dict2items }}"
  loop_control:
    label: "{{ item.key }}"

更新

如果您的组名不是有效的标识符(例如,它们有空格或-等),您需要在 jmespath 表达式中引用它们:

- set_fact:
    user_hosts: >-
      {{ user_hosts|combine({
      item.key:
      (
      item.value.hosts|default([])
      + user_groups|json_query('[{}][]'.format(
      ','.join(item.value.group|map('regex_replace', '(.*)', '"\1"'))
      ))
      )|unique
      }) }}
  loop: "{{ user_names|dict2items }}"

在这里,我们使用regex_replace过滤器并map在列表中的每个项目周围添加引号。


推荐阅读