Issue
I'm trying to generate a CSV output file with the following template: Hostname;Login;UID;GID;Comments;SUDO Enabled;Last_Login_Date
This task has to check this information for every user with UID >= 1000 based on info located at /etc/passwd. The comments is the same Comments field in /etc/passwd. If an user has SUDO privileges, it has to show just YES or NO. For the Last_Login_Date, if the user has never logged to this system, it should show N/A.
For this task, I have come with an Ansible playbook solution but I can't figure out how to fix the following error when creating the CSV file.
The playbook:
tasks:
- name: Check User List
shell:
cmd: |
awk -F ':' '{ if ($3 >= 999 && $7 != "/sbin/nologin") print $1 }' /etc/passwd
register: user_list_result
- name: Get info on /etc/passwd
ansible.builtin.shell: "grep ^{{ item }}: /etc/passwd"
loop: "{{ user_list_result.stdout_lines }}"
register: user_info_results
- name: Check SUDO Privileges
ansible.builtin.shell: "sudo -U {{ item.item }} -l | grep -q '(ALL) ALL' && echo 'YES' || echo 'NO'"
loop: "{{ user_info_results.results }}"
register: sudo_status_results
- name: Check Last Login Date
ansible.builtin.shell: "lastlog -u {{ item.item }} | awk 'NR==2 { print $4 }'"
loop: "{{ user_info_results.results }}"
register: last_login_results
- name: Build CSV File
ansible.builtin.template:
src: "userlist_template.j2"
dest: "output.csv"
vars:
user_info_results: "{{ user_info_results.results }}"
sudo_status_results: "{{ sudo_status_results.results }}"
last_login_results: "{{ last_login_results.results }}"
And I have come to this Jinja2 template:
Hostname;Login;UID;GID;Comments;SUDO Enabled;Last_Login_Date
{% for user_info in user_info_results %}
{{ inventory_hostname }};{{ user_info['item'] }};{{ user_info.stdout.split(':')[2] }};{{ user_info.stdout.split(':')[3] }};{{ user_info.stdout.split(':')[4] }};{{ sudo_status_results[loop.index0].stdout }};{{ last_login_results[loop.index0].stdout | default('N/A') }}
{% endfor %}
But when I run this playbook the following error occurs:
The error was: ansible.errors.AnsibleUndefinedVariable: 'str object' has no attribute 'item'
It seems that Ansible is checking for a string object but it's a dict object. How can I achieve the proposed file?
Solution
First, if you're not on Mac OS, I'd suggest you to use getent
module instead of parsing the files manually.
Now to the template structure. If we simplify it for debugging purposes, we'll see that the user_info_results
variable doesn't seem to have the structure you would expect:
Hostname;Login;UID;GID;Comments;SUDO Enabled;Last_Login_Date
{% for user_info in user_info_results %}
{{ inventory_hostname }};{{ user_info }}
{% endfor %}
Hostname;Login;UID;GID;Comments;SUDO Enabled;Last_Login_Date
localhost;results;
localhost;skipped;
localhost;changed;
localhost;msg;
So, using the nested results
object of your variables fixes the template:
Hostname;Login;UID;GID;Comments;SUDO Enabled;Last_Login_Date
{% for user_info in user_info_results.results %}
{{ inventory_hostname }};{{ user_info['item'] }};{{ user_info.stdout.split(':')[2] }};{{ user_info.stdout.split(':')[3] }};{{ user_info.stdout.split(':')[4] }};{{ sudo_status_results.results[loop.index0].stdout }};{{ last_login_results.results[loop.index0].stdout | default('N/A') }}
{% endfor %}
I decreased the UID to 280 for testing because I don't have any UIDs >= 1000 locally:
Hostname;Login;UID;GID;Comments;SUDO Enabled;Last_Login_Date
localhost;_coreml;280;280;CoreML Services;NO;
localhost;_sntpd;281;281;SNTP Server Daemon;NO;
localhost;_trustd;282;282;trustd;NO;
localhost;_darwindaemon;284;284;Darwin Daemon;NO;
localhost;_notification_proxy;285;285;Notification Proxy;NO;
localhost;_oahd;441;441;OAH Daemon;NO;
Why does it happen? While it looks kind of surprising, it's documented: the registered variables have higher precedence than task vars.
If we override the variables using set_facts
instead, your old template works as you expect:
- name: Override the variables
set_fact:
user_info_results: "{{ user_info_results.results }}"
sudo_status_results: "{{ sudo_status_results.results }}"
last_login_results: "{{ last_login_results.results }}"
- name: Build CSV File
ansible.builtin.template:
src: "userlist_template.j2"
dest: "output.csv"
Answered By - Alexander Pletnev Answer Checked By - Mildred Charles (WPSolving Admin)