--- - name: nested groups loop block: - name: Reset loop variables # without unsetting infinte loops will occur set_fact: group_present: false find_groups: [] #find_users: [] # we dont want to reset this var, this will append on each loop until exit (unexpected behaviour but works) find_group_members_tidy: [] group_members: [] samaccountname_tidy: [] samaccountname_group_members: [] member_type: [] member_attributes: [] # - name: Inspect dummy host object_attributes # debug: # msg: "{{ hostvars['DUMMY_HOST']['object_attributes'] }}" - name: Filter groups into a list - CHANGED set_fact: find_groups: "{{ find_groups | default([]) + [dict(name=item.name, role=item.role, type=item.type)] }}" with_items: "{{ hostvars['DUMMY_HOST']['object_attributes'] }}" when: item.type == 'Group' - name: Append users to list - CHANGED set_fact: find_users: "{{ find_users | default([]) + [dict(name=item.name, role=item.role, type=item.type)] }}" with_items: "{{ hostvars['DUMMY_HOST']['object_attributes'] }}" when: item.type == 'Person' # - debug: # msg: # - "{{ find_groups }}" # - "{{ find_users }}" # when: find_groups is defined - name: Query group members win_shell: ([ADSISearcher] "(sAMAccountName={{ item.name }})").FindOne().Properties.member register: find_group_members_result with_items: "{{ find_groups }}" when: find_groups is defined # - debug: # msg: "{{ find_group_members_result }}" # when: find_group_members_result is defined - name: Tidy group members into dict of names and role set_fact: find_group_members_tidy: '{{ find_group_members_tidy | default([]) + [dict(name=item.name, role=item.role)] }}' with_items: "{{ find_group_members_result | json_query(jmesquery) }}" vars: jmesquery: "results[].{role: @.item.role, name: @.stdout_lines}" when: find_groups is defined # new - did i miss this in the demo - name: Tidy group members into dict of name and role set_fact: group_members: "{{ group_members | default([]) + [dict(name=item.1.split(',')[0].split('CN=')[1], role=item.0.role)] }}" with_subelements: - "{{ find_group_members_tidy }}" - name when: find_groups is defined # new - did i miss this in the demo # - debug: # msg: "{{ group_members }}" # when: group_members is defined # the UoN account field distinguishedname uses the short account name the same as samaccountname - this is helpful when looking up group members # distinguishedname {CN=tseed,CN=Users,DC=netappsim,DC=local} # samaccountname {tseed} # # out of the box AD will have the full name of the user in the distinguishedname field # distinguishedname {CN=Toby Seed,CN=Users,DC=netappsim,DC=local} # samaccountname {tseed} # # an additional check follows to lookup a samaccountname using the distinguishedname, this maintains compatibility with non UoN AD - name: Find group members sAMAccountName win_shell: ([ADSISearcher] "(cn={{ item.name }})").FindOne().Properties.samaccountname register: samaccountname_result with_items: "{{ group_members }}" when: group_members is defined - name: Tidy group members sAMAccountName into list set_fact: samaccountname_tidy: "{{ samaccountname_tidy | default([]) + [(item.stdout_lines)[0]] }}" with_items: "{{ samaccountname_result.results }}" when: group_members is defined - name: Rebuild group_members dict with sAMAccountName set_fact: samaccountname_group_members: "{{ samaccountname_group_members | default([]) + [ dict(name=item[1], role=item[0].role) ] }}" loop: "{{ group_members|zip(samaccountname_tidy)|list }}" when: group_members is defined # - debug: # msg: "{{ samaccountname_group_members }}" # when: group_members is defined - name: copy samaccountname_group_members to group_members set_fact: group_members: "{{ samaccountname_group_members }}" when: samaccountname_group_members is defined # - debug: # msg: "{{ group_members }}" # when: group_members is defined - name: Check AD object is user or group win_shell: ([ADSISearcher] "(sAMAccountName={{ item.name }})").FindOne().Properties.objectcategory register: member_type_result with_items: "{{ group_members }}" when: group_members is defined # - debug: # msg: "{{ member_type_result }}" # when: group_members is defined - name: Build list of AD object type set_fact: member_type: "{{ member_type | default([]) + [(item.stdout.split(',')[0].split('CN=')[1])] }}" with_items: "{{ member_type_result.results }}" when: group_members is defined # - debug: # msg: "{{ member_type }}" # when: group_members is defined - name: Build dict of object names and types set_fact: member_attributes: "{{ member_attributes | default([]) + [ dict(name=item[0].name, role=item[0].role, type=item[1]) ] }}" # effectively adding a new positional field from the list to the dict loop: "{{ group_members|zip(member_type)|list }}" when: group_members is defined # - debug: # msg: "{{ member_attributes }}" # when: group_members is defined - name: Reset/Add variable object_attributes to dummy host for next loop add_host: name: "DUMMY_HOST" object_attributes: "{{ member_attributes }}" when: member_attributes is defined - name: Set flag to notify there are nested groups set_fact: group_present: true with_items: "{{ member_attributes }}" when: member_attributes is defined and item.type == 'Group' #when: member_attributes is defined and item.type == 'GroupA' # used to break loop with test for no group_present - name: Nested group present? fail: msg: Nested group detected, loop will run again when: group_present # the following tasks only run on the last run of the loop where there are no more groups detected # append the users from the last run of the loop to find_users # set find_users as a DUMMY_HOST variable to be retrieved from the calling script - this is how you pass variables back from different plays also works for include_tasks - name: Append users to list set_fact: find_users: "{{ find_users | default([]) + [dict(name=item.name, type=item.type, role=item.role)] }}" with_items: "{{ member_attributes }}" when: item.type == 'Person' # if the script was passed the members parameter containing only a group and the requester_user_ad is a member of this group # there wont be an entry in the find_users dict as this wont have been caught and assigned the role='requestor' in the calling script # all requester flag logic could be moved here, the calling script wouldnt need to pass the role key, we keep it for illustration as # this script was origionally designed to assign roles for different RWX permissions on GPFS # we add the user to the dict with role='requester' - name: Check if requestor was nested in group and not passed in the members parameter set_fact: requester_found: "{{ item.name }}" with_items: "{{ find_users }}" when: item.name == requester_user_ad - name: Add requestor if it was nested in group and not passed in the members parameter set_fact: find_users: "{{ find_users + [dict(name=requester_found, type='Person', role='requester')] }}" when: requester_found is defined - name: Add users to variable find_users for dummy host on final loop add_host: name: "DUMMY_HOST" find_users: "{{ find_users }}" when: find_users is defined rescue: - include_tasks: group_lookup.yml