User Creation: Better way to do this?

Newbie to ansible…

Use case: at the create time users are assigned a default password and will be asked to change the password at the first login

What I came up with (in a role)…

  • name: Create User
    user: name=“{{ item.username }}”
    groups=“{{ def_user_groups | union(item.groups) | join(‘,’) if item.groups is defined
    and item.groups is iterable
    else def_user_groups | join(”,“) }}”
    comment=“{{ item.name }}”
    uid=“{{ item.uid }}”
    password=“{{ def_user_passwd }}”
    update_password=on_create
    append=yes
    state=present
    with_items:
    add_users
    tags: ‘users’

  • name: List users who has default password
    shell: grep “{{ def_user_passwd }}” /etc/shadow | cut -d’:’ -f1
    register: users_with_def_passwd

  • name: Force user with default password to change password at next login
    command: chage -d 0 {{ item }}
    with_items: users_with_def_passwd.stdout.split()

Is there a better way to accomplish this? Feel like I should be able to somehow distinguish the newly created users and be able to do this more elegantly…

Thought I run it by the experts.

Appreciate your feedback.

Thanks

.raja

Unrelated feedback – The groups black magic you have on line 2 is definitely against the Ansible “keep it simple” motif by a large factor and would keep me from using Ansible if I saw it first on a blog post. I’d try to simplify that :slight_smile:

I’d just call the variable groups and do the join on it – and that’s it.

{{ groups.join(“,”) }}

I’m not sure what the other logic does.

Also the new line after the “with_items” can be removed.

with_items: add_users

Further

stdout.split()

can be replaced with

stdout_lines

Can also be written on the same line.

To your question though –

There is no chage setting for new user creation right now, so what you have is reasonable.

I believe you could just register the results of the user add step and then iterate over that:

  • name: Create User
    user: …
    register: created_users
    with_items: add_users
    tags:

  • users

  • name: Force new user to change password at next login
    command: chage -d 0 {{ item }}
    when: item.changed
    with_items: created_users.results

To make it somewhat easier, I’d be ok with modifications that made the user module return an added: True/False or something to compliment changed.

Michael,

Appreciate you taking the time to review and providing feedback…I am all for simplifying it. Here’s how I got to the group statement…

Every users will belong to a set of default groups, so if this list changes I don’t have to edit it for every users. e.g.

def_user_groups:

  • ‘cdrom’
  • ‘dip’
  • ‘plugdev’
  • ‘sambashare’

If at a later time, all users have to added to a new “public” group, I can simply modify this variable and rerun the user role to all hosts.

  • And then there are user specific group, e.g. additionally raja will be a sudo user

add_users:

  • username: raja
    name: Raja Mukherjee
    groups:

  • ‘sudo’

  • ‘adm’
    uid: 1002
    ssh_key_files:

  • id_rsa.pub

To accommodate this, my first iteration was

groups="{{ def_user_groups | union(item.groups) | join(‘,’) }} - simply merge the two lists

Well that broke the variable syntax when “groups” attribute is missing in variable declaration - for users who will only have the default groups.

  • username: andrew
    name: Andrew McCarthy
    uid: 1002

ssh_key_files:

  • id_rsa.pub

To accommodate that my group line evolved to
groups="{{ def_user_groups | union(item.groups) | join(‘,’) if item.groups is defined else def_user_groups | join(‘,’) }}

Otherwise I was getting an error saying that the group attribute is undefined. Extra check for iterator came later to accommodate syntax when “groups” is defined but empty

  • username: andrew
    name: Andrew McCarthy
    groups:
    uid: 1002

ssh_key_files:

  • id_rsa.pub

This use case (empty attribute) is not that important, and I will definitely skip that.

Is there a better way to accommodate/simplify this line?

groups="{{ def_user_groups | union(item.groups) | join(‘,’) if item.groups is defined else def_user_groups | join(‘,’) }}

Or any alternate design suggestion?

I already made the other suggested changes. Having fun learning new stuff.

Once again thank you for your time and help.

.raja

James,

Thanks for taking the time and reviewing the code…

In your suggested change, wouldn’t it trigger a chage for any changes? For example, appending a new group for a user will then force them to change password? Unless I am misunderstanding…

.raja

Untested:

vars:
my_groups: “{{ def_user_groups | union(item.groups|default()) | join(”,") }}

And keep your playbook clean:

{{ my_groups }}

Ansible will lazy-evaluate the variables.

But really even this is massively overcomplicated.

First add the user with the defined groups in with_items, if any, and then call the module again to append the groups from the other list, passing append=yes.

Michael,

Once again, thanks for your continued support.

I did not get this.

vars:
my_groups: “{{ def_user_groups | union(item.groups|default()) | join(”,") }}

Is there a provision to provide vars inside loop?

However, it did give me an idea and I was able to rewrite the variable initialized to ‘’ and remove the conditional statements from the tasks.

roles/users/defaults/main.yml

def_user_passwd: ‘$6$RGIwNOafbq$i870.hwdIBRoMOXSLjGZSDdZxl79sMCG7jxxv2nYqtCQWr26L6tKBnoRUIiYkc5xGFgg7oJJ/NhLQBsxpHGqh1’

def_user_groups:

  • ‘cdrom’
  • ‘dip’
  • ‘plugdev’
  • ‘sambashare’

roles/users/vars/main.yml

add_users:

  • username: mimi
    name: Mimi Mukherjee
    groups:

  • ‘sudo’

  • ‘adm’
    uid: 1002

  • username: andrew
    name: Andrew Mukherjee
    groups:
    uid: 1003

And the task

roles/users/tasks/main.yml

  • name: Create User
    user: name=“{{ item.username }}”
    groups=“{{ def_user_groups | union(item.groups) | join(‘,’) }}”
    comment=“{{ item.name }}”
    uid=“{{ item.uid }}”
    password=“{{ def_user_passwd }}”
    update_password=on_create
    append=yes
    state=present
    with_items: add_users
    tags: ‘users’

  • name: List users who has default password
    shell: grep “{{ def_user_passwd }}” /etc/shadow | cut -d’:’ -f1
    register: users_with_def_passwd
    tags: ‘users’

  • name: Force user with default password to change password at next login
    command: chage -d 0 {{ item }}
    with_items: users_with_def_passwd.stdout_lines
    tags: ‘users’

Thanks again

.raja

“Is there a provision to provide vars inside loop?”

You’ve lost me on this part - though glad to see things got cleaned up!

Thanks Michael, appreciate all your help…

.raja

Somehow I missed this post. This would be really helpful…

Thanks

.raja