使用多个子元素的最有效方法是什么?

时间:2019-05-01 14:44:45

标签: python ansible jinja2

我正在使用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剧本已在所有主机上运行,​​并且仅当用户通过组或按每个用户定义的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 }} ))

这一直很好,但是现在我有一个要求,以确保没有与之关联的所有主机上都不存在该用户。这里的主要用例是经历了组/用户的用户。主机更改,但它们仍将存在于不再关联的服务器上。

如果我尝试上面发布的相同代码块,但是将when的条件取反并将state更改为absent,由于循环的性质,它将无法正常工作。在特定主机上运行任务时,我们会循环浏览分组列出的每个服务器。并非每个迭代都会匹配inventory_hostname,因此ansible将抢先删除用户,即使该关联将在以后的迭代中匹配。

我想只有在循环的所有迭代都符合条件的情况下,我才试图找出如何运行任务的方法。或者也许有一种更优雅的方式来解决问题。

预期结果:

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

从用户中删除一组。运行ansible,并希望将用户从不再与之关联的主机中删除。

我尝试使用subelements jinja过滤器product嵌套unions,但似乎无法弄清楚这一点。

感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

阅读您的评论后,我会更好地了解您要做什么。这是另一种方法:构建用户应在其上存在的主机的统一列表。

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

---
- 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_names中明确声明的。之所以在host0上定义她是因为她是developers组的成员。

在此示例中,

bob最终在所有三台主机上定义:host0,因为在user_names中明确声明了该声明,而host1host2由于他在robotstests组中的成员身份。

有了此列表后,创建和删除用户很简单:

- 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在列表中的每个项目周围添加引号。