Ansible: How to create uid's within certain range - ansible

I am currently working on a host where i have installed ansible. I have created 2 application accounts with groups with nologin and within that groups i want to add users, so that every department has their own ansible directory.
My vars look like below:
---
- hosts: localhost
become: yes
vars:
ansible_groupuser:
- name: "ansible-dictators"
ansible_groupuser_uid: "3000"
ansible_users:
- idia
- josefs
- donaldt
- kimjongu
- name: "ansible-druglords"
ansible_groupuser_uid: "3001"
ansible_users:
- pabloe
- javierg
- frankl
- rossu
Now i have 2 plays. 1 to create the Groupuser:
# This creates the groupuser
- name: Play 1 Create central ansible user and group per department
user:
name: "{{ item.name }}"
shell: "/sbin/nologin"
home: "/home/{{ item.name }}"
comment: "{{ item.name }} Group Account"
uid: "{{ item.ansible_groupuser_uid }}"
append: "yes"
with_items:
- "{{ansible_groupuser}}"
And 1 to create the "normal" users:
- name: Play 2 Create users
user:
name: "{{ item.1 }}"
shell: "/bin/bash"
home: "/home/{{ item.1 }}"
comment: "{{ item.1 }}"
groups: "{{ item.0.name }}"
append: "yes"
with_subelements:
- "{{ ansible_groupuser }}"
- ansible_users
If i run this play it creates the groupuser ansible-dictators on 3000 and ansible-druglords on 3001. idia gets 3002, josefs gets 3003 etc. It gets kinda messy, when i want to add a 3th groupuser like ansible-rockstars, it starts counting at the first available uid, 3010. What i want is to place the groupusers and the common users in 2 different ranges (2000 and 3000 for example)
When i do a with_together on the first play, like below, it works:
- name: Play1 Create central ansible user and group per department
user:
name: "{{ item.0.name }}"
shell: "/sbin/nologin"
home: "/home/{{ item.0.name }}"
comment: "{{ item.0.name }} Group Account"
uid: "{{ item.1 }}"
append: "yes"
with_together:
- "{{ansible_groupuser}}"
- "{{ range(3000,3020)|list }}"
when: item.0 != None
But when i do a with_together on the second play, it doesnt work:
- name: Create users
user:
name: "{{ item.1 }}"
shell: "/bin/bash"
home: "/home/{{ item.1 }}"
comment: "{{ item.1 }}"
groups: "{{ item.0.name }}"
append: "yes"
uid: "{{ item.2 }}"
with_together:
- "{{ ansible_groupuser }}"
- ansible_users
- "{{ range(2000,2020)|list }}"
Anyone got a suggestion how to make the second play work with a uid in a certain range? Or another suggestion how to get the uid's in different groups? To give the groupusers an uid in the vars is no problem. But i am expecting a lot of "common" users to add (+50) and i dont want to specify a uid for all of those users.
Hope it makes sense. Thanks in advance.

I think range(...) approach has a flaw: if you delete some user from your list in the future, IDs for subsequent entries will change and you can end up with messed file permissions on your system.
You can patch user module to support --firstuid/--lastuid arguments of the underlying adduser command, so you can set different range for uid generation.
But I'd suggest you to define "static" uids for top-level users in your vars file (from some predefined range, say: 3000..30xx) – this way you can safely add/remove top-level user/groups in the future.
And leave "common" users to get their ids automatically, so adding/deleting them will not mess your ids. If you like them to be from some specific range, you can modify system-wide /etc/adduser.conf with FIRST_UID=5000/LAST_UID=6000.

Related

Ansible can't loop through subelements in variables files

I have the following user lists in separated files.
The idea behind this is to create multiple users and assign them to different user groups.
To make it easier, I shortened the list. I reality they include passwords and etc.
First variables file
userlist-os:
group: os
users:
- comment: Test User
username: ostest1
user_id: 9404
user_state: present
- comment: Test User
username: ostest2
user_id: 9405
user_state: present
Second variables file
userlist-zos:
group: zos
users:
- comment: Test User1
username: zostest1
user_id: 9204
user_state: present
- comment: Test User2
username: zostest2
user_id: 9205
user_state: present
This is how my playbook looks like:
- name: test
hosts: all
user: root
vars_files:
- [userlist-zos.yml]
- [userlist-os.yml]
tasks:
- name: Create user accounts
user:
name: "{{ item.users.username }}"
update_password: on_create
uid: "{{ item.users.user_id }}"
shell: /bin/bash
create_home: yes
group: "{{ item.group }}"
state: present
comment: "{{ item.users.comment }}"
when: item.users.user_state == 'present'
with_items:
- "{{ userlist-os }}"
- "{{ userlist-zos }}"
The problem is that I'm not getting into the sub elements of users(variable username is undefined), but when I set an index like this name: "{{ item.users.0.username }}" I do get the first username from each file.
Any help is appreciated.
In your scenario, item.users are lists of users, they are not dictionaries. Therefore they don't have username field, they have list elements which have that field instead. You were able to access to first element of the list with "item.users.0.username". What I suggest you to do is to access these nested variables with an include_task variable as follows:
main.yaml
- name: Trial
hosts: localhost
vars:
# YOUR VARS
tasks:
- name: Create user accounts
include_tasks: helper.yml
with_items:
- "{{ userlistos }}"
- "{{ userlistzos }}"
loop_control:
loop_var: list
helper.yml
- name: Create user accounts
user:
name: "{{ item.username }}"
update_password: on_create
uid: "{{ item.user_id }}"
shell: /bin/bash
create_home: yes
group: "{{ list.group }}"
state: present
comment: "{{ item.comment }}"
when: item.user_state == 'present'
with_items:
- "{{list.users}}"

Create only those users from a list that do not exist

I have a list of users and I only want to create those, which do not exist on the system.
This is what I have tried:
- name: Connection to Unix server
hosts: localhost
vars:
USER_ID_details:
- user_id: my_user1
groups: wheel
real_full_name: my_user_name1
affected_host: localhost
email_id: my_user1#ibm.com
- user_id: my_user2
groups: wheel
real_full_name: my_user_name2
affected_host: localhost
email_id: my_user2#ibm.com
tasks:
- name: check for the ID is present
#shell: "id {{ item.user_id }}"
shell: grep "{{ item.user_id }}" /etc/passwd | awk -F":" '{print $1}'
loop: "{{ USER_ID_details }}"
ignore_errors: true
register: id_check
- name: setting var
set_fact:
user_id_names1: "{{ user_id_names1|default([]) + [item.stdout] }}"
with_items: "{{ id_check.results }}"
when: item.stdout != ""
- debug: var=user_id_names1
- block:
- name: create Linux user as per specification
user:
name: "{{ item.user_id }}"
password: "{{ pass_reg.stdout_lines[0] | password_hash('sha512') }}"
group: "{{ group_name }}"
groups: "{{ item.groups }}"
comment: "{{ comment }}"
shell: "{{ user_shell }}"
#uid: "{{ uid_num.item }}"
home: "/home/{{ item.user_id}}"
loop: "{{ USER_ID_details }}"
when:
- os_type == "RedHat"
- "{{ item.user_id }} not {{id_check.results}}"
What could be the best way to check if user exists, and only add those, that don't exist on server. I'm trying to check the user_id_names1 list of ids generated against list dictionary USER_ID_details and filter the existing ones.
As Vladimir Botka stated on the comment, ansible does that already. Most modules (including the user module) will ensure that the state you specify will be present on the machine, after ansible ran.
For example, if you specify that a certain user exists on the system, it will after you ran the playbook. It will be created if it didn't exist before, but it will not be added, if it already existed.
The catch is, that ansible will try to create the state you specified, possibly changing your existing users.
For example, let's assume your user already exists, but has changed the default shell to /bin/zsh while in your playbook you specify, that it should have /bin/bash. In that case, ansible will change the default shell to /bin/bash whenever you run your playbook.
If you don't care about existing users being modified (or you are sure they never will be) you can just run the user module for all users every time, as users will not be added twice.
Otherwise you can do this to check if a user exists and only add it if it does not:
tasks:
- name: get list of existing users
getent:
database: passwd
- name: get list of existing usernames
set_fact:
existing_users: "{{ ansible_facts.getent_passwd.keys() | list }}"
- name: create Linux user as per specification
user:
name: "{{ item.user_id }}"
password: "{{ pass_reg.stdout_lines[0] | password_hash('sha512') }}"
group: "{{ group_name }}"
groups: "{{ item.groups }}"
comment: "{{ comment }}"
shell: "{{ user_shell }}"
home: "/home/{{ item.user_id}}"
loop: "{{ USER_ID_details }}"
when: item.user_id not in existing_users
Make sure to read the documentation of the user module and that you understand what all the options do.
For example, the password option will set the password of that user to the specified value. If the user changed his password, you will change it back every time you run the playbook. Set update_password: on_create to prevent that.
You are also setting the primary group of all users to the same value (in group_name). Make sure that is what you actually want to do.
#toydarian Used below method too when i didnot know about the getent option.
- name: check for the ID is present
#shell: "id {{ item.user_id }}"
shell: grep "{{ item.user_id }}" /etc/passwd | awk -F":" '{print $1}'
loop: "{{ USER_ID_details }}"
ignore_errors: true
register: id_check
- name: setting var
set_fact:
user_id_names1: "{{ user_id_names1|default([]) + [item.stdout] }}"
with_items: "{{ id_check.results }}"
when: item.stdout != ""
- debug: var=user_id_names1
- name: create Linux user as per specification
user:
name: "{{ item.user_id }}"
password: "{{ pass_reg.stdout_lines[0] | password_hash('sha512') }}"
group: "{{ group_name }}"
groups: "{{ item.groups }}"
comment: "{{ item.real_full_name }}"
shell: "{{ user_shell }}"
#uid: "{{ uid_num.item }}"
home: "/home/{{ item.user_id}}"
loop: "{{ USER_ID_details }}"
when:
- os_type == "RedHat"
- item.user_id not in user_id_names1

Can we have 2 with_items in ansible in a single task

Below is the condition
- name: Find the image
slurp:
src: "{{ IMAGE }}"
register: slurp_results
- name: Upload image
shell: |
skopeo copy -docker-archive:{{ item }}.tar docker://{{ URL }}/TESTIMAGE
with_items: "{{ (slurp_results.content|b64decode).splitlines() }}"
The above code works.
But I would need "TESTIMAGE" also to be replaced as {{ item }} like below.
skopeo copy -docker-archive:{{ item }}.tar docker://{{ URL }}/{{ item }}
How to define 2 with_items in a single shell task with 2 different slurp results
I believe you can by using the subelements module. Here is a link. Try going by this example:
- name: Setup MySQL users, given the mysql hosts and privs subkey lists
mysql_user:
name: "{{ item.0.name }}"
password: "{{ item.0.mysql.password }}"
host: "{{ item.1 }}"
priv: "{{ item.0.mysql.privs | join('/') }}"
with_subelements:
- "{{ users }}"
- mysql.hosts
Users is referred to as item.0 and hosts as item.1 and so on.

Registering and using multiple variables in Ansible

I'm trying to pop VM instances, put them into different host groups (say webservers and devops/admin machines) and install what is needed on them in one single playbook.
I don't know what IP addresses, for instance, GCP will give these instances, and so i am trying to capture them in a variable for use later on in the playbook. I can capture them fine by using "register" but using them is proving tricky. For instance if I do.
- name: création des adresses statiques
gcp_compute_address:
name: "{{ item }}"
state: present
region: "{{ region }}"
project: "{{ gcp_project }}"
auth_kind: "{{ gcp_cred_kind }}"
service_account_file: "{{ gcp_cred_file }}"
scopes:
- https://www.googleapis.com/auth/compute
loop:
- adresse-1
- adresse-2
- adresse-3
- adresse-4
- adresse-5
register: address
The best way i have figured out to use these variables later on is:
network_interfaces:
- network: "{{ network.name }}"
access_configs:
- name: 'External NAT'
type: 'ONE_TO_ONE_NAT'
nat_ip:
- "{{ address.results[0].address }}"
- "{{ address.results[1].address }}"
- "{{ address.results[2].address }}"
Which fails miserably.
Please help ? How can I use the range of addresses I have created ?
I am going nuts over this
It is possible to add_host to the group webservers and proceed with the next play
- add_host:
name: "{{ item }}"
groups: webservers
loop: "{{ address.results|json_query('[*].address') }}"
- debug:
msg: "{{ groups['webservers'] }}"
- hosts: webservers
tasks:
- name: Configure cluster
...
The tasks below split the hosts into two groups
- set_fact:
my_hosts: "{{ address.results|json_query('[*].address') }}"
- add_host:
name: "{{ item }}"
groups: webservers1
loop: "{{ my_hosts[0:(my_hosts|length / 2)|int] }}"
- add_host:
name: "{{ item }}"
groups: webservers2
loop: "{{ my_hosts[(my_hosts|length / 2)|int:my_hosts|length] }}"
- debug:
msg: "{{ groups['webservers1'] }}"
- debug:
msg: "{{ groups['webservers2'] }}"
There is also GCE Dynamic Inventory and other 100+ gcp modules. You might want to start with Google Cloud Platform Guide.
FWIW, Ansible 2 Cloud Automation Cookbook covers leading providers incl. GCP.

Ansible with subelements referencing a dict

Bear with me, please. I've never had to do something this complex with Ansible and I'm really struggling to piece it together.
To sum it up, I already have a dict and a task to deploy our employee's SSH accounts and public keys to our servers. I would like to re-use this dict to also deploy certain employee keys to certain website user accounts. An example probably explains better than I can.
employee_ssh_users:
user1: 'user1key'
user2: 'user2key'
user3: 'user3key'
user4: 'user4key'
- name: Add employee SSH users
user:
name: "{{ item.key }}"
state: present
with_dict: "{{ employee_ssh_users }}"
- name: Add employee public keys to employee accounts
authorized_key:
user: "{{ item.key }}"
state: present
key: "{{ item.value }}"
with_dict: "{{ employee_ssh_users }}"
The above configuration and tasks work fine for adding our employees and their keys to the servers. Now, I want to re-use these keys so that I can add certain employees to certain other users without having to copy and paste the employee's keys. Here is what I'm trying to do:
website_keys:
- name: site1
authorized:
- user1
- user3
- name: site2
authorized:
- user1
- user2
- name: Add employee public keys to website accounts
authorized_key:
user: "{{ item.0.name }}"
key: "{{ hostvars[inventory_hostname]['employee_ssh_users'][' + item.1 '] }}"
with_subelements:
- "{{ website_keys }}"
- authorized
Basically, I can't figure out exactly what I need to do to interpolate the subelement into the key variable, if it's even possible at all.
It's quite simple:
- name: Add employee public keys to website accounts
authorized_key:
user: "{{ item.0.name }}"
key: "{{ employee_ssh_users[item.1] }}"
with_subelements:
- "{{ website_keys }}"
- authorized
You can query employee_ssh_users by name and use item.1 without quotes, as it is a variable itself.

Resources