Search code examples
dictionarymergeansible

Ansible: How to merge two different sized lists of dicts


a task in ansible 2.10.8:

I would like to merge two different lists of dicts to get one complemented with missing items, but not add a dict to the first list.

List of dicts one:

user_users:
  - username: 'foo'
    primarygid: 42
  - username: 'bar'

List of dicts two:

user_users_skel:
  - username: foo
    uid: 600
    primarygid: 600
    shell: '/bin/bash'
    ssh_login: false
  - username: bar
    uid: 602
    primarygid: 602
    shell: '/bin/bash'
    ssh_login: true
  - username: blupp
    uid: 1000
    primarygid: 1000
    shell: '/bin/bash'
    ssh_login: true
    default_groups: 'admin'
    initial_password_hash: '$2349887rhfewklrjn'

Expected Output (also a list of dicts):

  - username: foo
    uid: 600
    primarygid: 42
    shell: '/bin/bash'
    ssh_login: false
  - username: bar
    uid: 602
    primarygid: 602
    shell: '/bin/bash'
    ssh_login: true

I used different approaches, like

    - set_fact:
        eff_users: "{{ user_users | ansible.builtin.combine(user_users_skel) }}"
    - ansible.builtin.debug:
        var: eff_users

with bogus output.


Solution

    • Merge the lists
      users_all: "{{ [user_users, user_users_skel] |
                     community.general.lists_mergeby('username') }}"
    

    gives

      users_all:
      - primarygid: 602
        shell: /bin/bash
        ssh_login: true
        uid: 602
        username: bar
      - default_groups: admin
        initial_password_hash: $2349887rhfewklrjn
        primarygid: 1000
        shell: /bin/bash
        ssh_login: true
        uid: 1000
        username: blupp
      - primarygid: 600
        shell: /bin/bash
        ssh_login: false
        uid: 600
        username: foo
    
    • Get the list usernames
      usernames: "{{ user_users | map(attribute='username') }}"
    

    gives

      usernames:
      - foo
      - bar
    
    • Select username in the list
      eff_users: "{{ users_all | selectattr('username', 'in', usernames) }}"
    

    gives what you want

      eff_users:
      - primarygid: 602
        shell: /bin/bash
        ssh_login: true
        uid: 602
        username: bar
      - primarygid: 600
        shell: /bin/bash
        ssh_login: false
        uid: 600
        username: foo
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        user_users:
          - username: foo
            primarygid: 42
          - username: bar
    
        user_users_skel:
          - username: foo
            uid: 600
            primarygid: 600
            shell: /bin/bash
            ssh_login: false
          - username: bar
            uid: 602
            primarygid: 602
            shell: /bin/bash
            ssh_login: true
          - username: blupp
            uid: 1000
            primarygid: 1000
            shell: /bin/bash
            ssh_login: true
            default_groups: admin
            initial_password_hash: $2349887rhfewklrjn
    
        users_all: "{{ [user_users, user_users_skel] |
                       community.general.lists_mergeby('username') }}"
        usernames: "{{ user_users | map(attribute='username') }}"
        eff_users: "{{ users_all | selectattr('username', 'in', usernames) }}"
            
      tasks:
    
        - debug:
            var: users_all
        - debug:
            var: usernames
        - debug:
            var: eff_users