Ansible script to Assign tickets to a list of users using round-robin method

75 Views Asked by At

I have two lists:

inc_numbers: [A, B, C, D, E, F, G] 

users:
- username: u1
  status: active
  
- username: u2
  status: active
  
- username: u3
  status: active

- username: u4
  status: active

Say the first list is tickets and the second list is users. How do I loop trough this? How do I assign these tickets in round-robin manner using Ansible?

I tried the following

  - name: with_together -> loop
    set_fact:
      inc_user_map: "{{ inc_user_map|default([]) + [{ 'inc_numbers':item.0, 'users':item.1.username }] }}"
      with_together:
      - "{{ inc_numbers }}"
      - "{{ users }}"`

  - name: Nested loop
    set_fact:
        inc_sel: "{{ inc_sel|d([]) + [item|combine({'selection': selection})] }}"
    loop: "{{ users }}"
    vars:
      username: "{{ item.username }}"
      selection: "{{ inc_numbers|
                     zip(username)|
                     rejectattr('0', 'eq', '0') }}"

This does'nt give the desired output. It ends after completing single loop of users

Desired Output

A : u1 
B : u2 
C : u3 
D : u1 
E : u2
F : u3 
G : u1
3

There are 3 best solutions below

0
Marcin Słowikowski On BEST ANSWER

This example does what you ask and also allows you to exclude inactive users

---
- name: Assign Tickets in Round-Robin
  hosts: localhost
  gather_facts: false
  vars:
    inc_numbers: [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
    users:
      - username: u1
        status: active
      - username: u2
        status: inactive
      - username: u3
        status: active
      - username: u4
        status: active
      - username: u5
        status: active
      - username: u6
        status: active
      - username: u7
        status: active

  tasks:
    - name: Assign ticket to user
      set_fact:
        inc_sel: "{{ inc_sel | default({}) | combine({ item: active_users[idx % (active_users | length)].username }) }}"
      loop: "{{ inc_numbers }}"
      loop_control:
        index_var: idx
      vars: 
        active_users: "{{ users | selectattr('status', 'eq', 'active') | list }}"
    - name: Print inc_sel
      debug:
        var: inc_sel

Output:

PLAY [Assign Tickets in Round-Robin] ******************************************************************************************************************************************************************************

TASK [Assign ticket to user] **************************************************************************************************************************************************************************************
ok: [localhost] => (item=A)
ok: [localhost] => (item=B)
ok: [localhost] => (item=C)
ok: [localhost] => (item=D)
ok: [localhost] => (item=E)
ok: [localhost] => (item=F)
ok: [localhost] => (item=G)
ok: [localhost] => (item=H)
ok: [localhost] => (item=I)
ok: [localhost] => (item=J)
ok: [localhost] => (item=K)
ok: [localhost] => (item=L)
ok: [localhost] => (item=M)
ok: [localhost] => (item=N)
ok: [localhost] => (item=O)
ok: [localhost] => (item=P)
ok: [localhost] => (item=Q)
ok: [localhost] => (item=R)
ok: [localhost] => (item=S)
ok: [localhost] => (item=T)
ok: [localhost] => (item=U)
ok: [localhost] => (item=V)
ok: [localhost] => (item=W)
ok: [localhost] => (item=X)
ok: [localhost] => (item=Y)
ok: [localhost] => (item=Z)

TASK [Print inc_sel] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
    "inc_sel": {
        "A": "u1",
        "B": "u3",
        "C": "u4",
        "D": "u5",
        "E": "u6",
        "F": "u7",
        "G": "u1",
        "H": "u3",
        "I": "u4",
        "J": "u5",
        "K": "u6",
        "L": "u7",
        "M": "u1",
        "N": "u3",
        "O": "u4",
        "P": "u5",
        "Q": "u6",
        "R": "u7",
        "S": "u1",
        "T": "u3",
        "U": "u4",
        "V": "u5",
        "W": "u6",
        "X": "u7",
        "Y": "u1",
        "Z": "u3"
    }
}

PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Alternative version

---
- name: Assign Tickets in Round-Robin
  hosts: localhost
  gather_facts: false
  vars:
    inc_numbers: [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
    users:
      - username: u1
        status: active
      - username: u2
        status: inactive
      - username: u3
        status: active
      - username: u4
        status: active
      - username: u5
        status: active
      - username: u6
        status: active
      - username: u7
        status: active  

  tasks:
    - name: Assign ticket to user
      set_fact:
        inc_sel: "{ {% for inc in inc_numbers %}'{{ inc }}':'{{active_users[loop.index0 % (active_users | length)].username}}',{% endfor %} }"
      vars: 
        active_users: "{{ users | selectattr('status', 'eq', 'active') | list }}"
    - name: Print inc_sel
      debug:
        var: inc_sel

Output:

PLAY [Assign Tickets in Round-Robin] ********************************************************************************************************************************************************************************************************

TASK [Assign ticket to user] ****************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [Print inc_sel] ************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "inc_sel": {
        "A": "u1",
        "B": "u3",
        "C": "u4",
        "D": "u5",
        "E": "u6",
        "F": "u7",
        "G": "u1",
        "H": "u3",
        "I": "u4",
        "J": "u5",
        "K": "u6",
        "L": "u7",
        "M": "u1",
        "N": "u3",
        "O": "u4",
        "P": "u5",
        "Q": "u6",
        "R": "u7",
        "S": "u1",
        "T": "u3",
        "U": "u4",
        "V": "u5",
        "W": "u6",
        "X": "u7",
        "Y": "u1",
        "Z": "u3"
    }
}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
0
Zeitounator On

The following self explanatory and complete playbook meets your requirement:

---
- hosts: localhost
  gather_facts: false

  vars:
    inc_numbers: [ A, B, C, D, E, F, G ]

    users:
      - username: u1
        status: active
      - username: u2
        status: active
      - username: u3
        status: active
      - username: u4
        status: active

    user_rounds: "{{ (inc_numbers | length / users | length) | round(0, 'ceil') }}"

    repeated_users_names_only: "{{ users | map(attribute='username') * user_rounds | int  }}"

    zipped_tickets_to_user: "{{ inc_numbers | zip(repeated_users_names_only) }}"

    ticket_to_user_dict: "{{ dict(zipped_tickets_to_user) }}"


  tasks:
    - name: "Count how many times you need to repeat your full user list
        to cover the number of tickets"
      ansible.builtin.debug:
        var: user_rounds

    - name: "Extract the user names and repeat them as needed"
      ansible.builtin.debug:
        var: repeated_users_names_only

    - name: "Zip together the elements from tickets and user names"
      ansible.builtin.debug:
        var: zipped_tickets_to_user

    - name: "Use the zipped list as key/value pairs to create a dict"
      ansible.builtin.debug:
        var: ticket_to_user_dict

Running the above playbook gives:

PLAY [localhost] *********************************************************************************************************************************************************************************************************************

TASK [Count how many times you need to repeat your full user list to cover the number of tickets] ************************************************************************************************************************************
ok: [localhost] => {
    "user_rounds": "2.0"
}

TASK [Extract the user names and repeat them as needed] ******************************************************************************************************************************************************************************
ok: [localhost] => {
    "repeated_users_names_only": [
        "u1",
        "u2",
        "u3",
        "u4",
        "u1",
        "u2",
        "u3",
        "u4"
    ]
}

TASK [Zip together the elements from tickets and user names] *************************************************************************************************************************************************************************
ok: [localhost] => {
    "zipped_tickets_to_user": [
        [
            "A",
            "u1"
        ],
        [
            "B",
            "u2"
        ],
        [
            "C",
            "u3"
        ],
        [
            "D",
            "u4"
        ],
        [
            "E",
            "u1"
        ],
        [
            "F",
            "u2"
        ],
        [
            "G",
            "u3"
        ]
    ]
}

TASK [Use the zipped list as key/value pairs to create a dict] ***********************************************************************************************************************************************************************
ok: [localhost] => {
    "ticket_to_user_dict": {
        "A": "u1",
        "B": "u2",
        "C": "u3",
        "D": "u4",
        "E": "u1",
        "F": "u2",
        "G": "u3"
    }
}

PLAY RECAP ***************************************************************************************************************************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
0
Vladimir Botka On

Given the below variables for testing

  inc_numbers: [A, B, C, D, E, F, G]
  users:
    - {username: u1, status: active}
    - {username: u2, status: active}
    - {username: u3, status: active}
    - {username: u4}
  • Get the active users
  active: "{{ users | json_query('[?status == `active`].username') }}"

gives

  active: [u1, u2, u3]
  inc_batch: "{{ inc_numbers | batch(active|length) }}"

gives

  inc_batch:
    - [A, B, C]
    - [D, E, F]
    - [G]
  • Iterate the batch, create dictionaries, and combine them
  inc_sel: |
    {% filter combine %}
    [{% for i in inc_batch %}
    {{ dict(i|zip(active)) }},
    {% endfor %}]
    {% endfilter %}

gives what you want

  inc_sel:
    A: u1
    B: u2
    C: u3
    D: u1
    E: u2
    F: u3
    G: u1

Example of a complete playbook for testing

- hosts: all

  vars:

    inc_numbers: [A, B, C, D, E, F, G]
    users:
      - {username: u1, status: active}
      - {username: u2, status: active}
      - {username: u3, status: active}
      - {username: u4}

    active: "{{ users | json_query('[?status == `active`].username') }}"
    inc_batch: "{{ inc_numbers | batch(active|length) }}"
    inc_sel: |
      {% filter combine %}
      [{% for i in inc_batch %}
      {{ dict(i|zip(active)) }},
      {% endfor %}]
      {% endfilter %}

  tasks:

   - debug:
        var: active | to_yaml
   - debug:
        var: inc_batch | to_yaml
   - debug:
        var: inc_sel | type_debug
   - debug:
        var: inc_sel