Ansible to create users and assign them to a particular group based on vars - linux

Gurus, I'm learning ansible and trying to understand how the variables can be used, i have come across writing below playbook but i'm not understanding How to assign a particular group to particular users based on the Variables i have defined in the play for both Users and Groups under vars
I have below play where i want to create Users anika and rigved and want to assign them to a docker group while ayush and eshant should have the test group assigned.
I'm not getting the idea to achieve this so far. However as of now while running it creates users and assigns both group to all users.
$ cat nested_playbook-1.yml
---
- name: testing nested play
hosts: localhost
vars:
users:
- anika
- rigved
- ayush
- eshant
grps:
- docker
- test
tasks:
- name: make users members of groups
user:
name: "{{ item[0] }}"
state: present
groups: "{{ item[0] }}"
with_nested:
- "{{ users }}"
- "{{ grps }}"

The way you've structured your data doesn't show any relationship between users and groups. One option would be to structure it like this:
grps:
- name: docker
users:
- anika
- rigved
- name: test
users:
- ayush
- eshant
With this structure, you can loop over grps using the subelements filter, like this:
---
- name: testing nested play
gather_facts: false
hosts: localhost
vars:
grps:
- name: docker
users:
- anika
- rigved
- name: test
users:
- ayush
- eshant
tasks:
- debug:
msg:
user:
name: "{{ item.1 }}"
state: present
groups: "{{ item.0.name }}"
loop: "{{ grps|subelements('users') }}"
loop_control:
label: "{{ item.1 }}"
The subelements filter is a way of creating a "nested loop": it iterates over each member of the 'users' key for each group in grps. During each loop iteration, item is a 2-tuple in which the first item is the corresponding element from grps and the second item iterates over the users key for that element. In other words, you end up iterating over this list:
[{'name': 'docker', 'users': ['anika', 'rigved']}, anika}
[{'name': 'docker', 'users': ['anika', 'rigved']}, rigved}
[{'name': 'test', 'users': ['ayush', 'eshant']}, ayush}
[{'name': 'test', 'users': ['ayush', 'eshant']}, eshant}
So within the loop, item.0 refers to the group entry itself (and thus item.0.name is the group name), and item.1 refers to the user.
PLAY [testing nested play] *******************************************************************
TASK [debug] *********************************************************************************
ok: [localhost] => (item=anika) => {
"msg": {
"user": {
"groups": "docker",
"name": "anika",
"state": "present"
}
}
}
ok: [localhost] => (item=rigved) => {
"msg": {
"user": {
"groups": "docker",
"name": "rigved",
"state": "present"
}
}
}
ok: [localhost] => (item=ayush) => {
"msg": {
"user": {
"groups": "test",
"name": "ayush",
"state": "present"
}
}
}
ok: [localhost] => (item=eshant) => {
"msg": {
"user": {
"groups": "test",
"name": "eshant",
"state": "present"
}
}
}
PLAY RECAP ***********************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Alternately, you could structure your data like this:
users:
- name: anika
group: docker
- name: rigved
group: docker
- name: ayush
group: docker
- name: eshant
group: docker
You can just use a simple loop to process this structure:
---
- name: testing nested play
gather_facts: false
hosts: localhost
vars:
users:
- name: anika
group: docker
- name: rigved
group: docker
- name: ayush
group: docker
- name: eshant
group: docker
tasks:
- debug:
msg:
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.group }}"
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"
Note that in both the above examples I'm using loop_control on the tasks just to set the loop label and make the output look nicer. You could remove loop_control from the above tasks with no impact on how they operate.

Related

Conditionals against variables

I am building a snapshot management playbook and that checks for an existing snap before taking a snap. I am having trouble testing a conditional when the playbook finds an existing snap. Here is a sample code:
---
- name: Test Snapshot
hosts: axwayT
gather_facts: false
vars_files:
- vault/creds.yml
vars:
mail_body_file: "/tmp/ansible_mail"
pre_tasks:
- name: Delete mail body file
file:
state: absent
path: "{{ mail_body_file }}"
delegate_to: localhost
run_once: true
- name: Create mail body file
file:
state: touch
path: "{{ mail_body_file }}"
delegate_to: localhost
run_once: true
tasks:
- name: find guest's folder using name
vmware_guest_find:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: no
name: "{{ vcenter_hostgroup_name }}"
datacenter: "{{ datacenter_name }}"
register: vm_folder
delegate_to: localhost
- name: Check for existing snapshots
vmware_guest:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: no
name: "{{ vcenter_hostgroup_name }}"
datacenter: "{{ datacenter_name }}"
folder: "{{vm_folder.folders[0]}}"
name: "{{ vcenter_hostgroup_name }}"
state: present
register: vm_state
delegate_to: localhost
- name: debug
debug:
var: vm_state.instance.current_snapshot
- name: Test
shell: echo "Found!"
when: vm_state.instance.current_snapshot !=""
I am working with an inventory of two servers for testing, nothing special. Here is the ouput I want to use a conditional against:
TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [swipe901.test.com] => {
"vm_state.instance.current_snapshot": ""
}
ok: [swipe902.test.com] => {
"vm_state.instance.current_snapshot": {
"creation_time": "2023-02-07T21:14:06.812901+00:00",
"description": "",
"id": 13,
"name": "VM Snapshot 2%2f7%2f2023, 3:14:05 PM",
"state": "poweredOn"
}
}
I tried two when statements:
when: vm_state.instance.current_snapshot =""
when: vm_state.instance.current_snapshot is defined
My logic is way off and I am seeing my own limitations with programing logic, which I plan to fix soon.
My plan for the logic is to, skip a step, in my playbook if vm_state.instance.current_snapshot is "". How would this be handled?
Q: "I would like the condition to evaluate false if there is anything populated in current_snapshot."
A: For example, the playbook
shell> cat pb.yml
- name: Evaluate false if there is anything populated in current_snapshot
hosts: localhost
tasks:
- debug:
msg: The variable current_snapshot is either empty or does not exist.
when: current_snapshot|d('', true)|length == 0
gives
shell> ansible-playbook pb.yml
PLAY [Evaluate false if there is anything populated in current_snapshot] *************************************************************
TASK [debug] *************************************************************************************************************************
ok: [localhost] =>
msg: The variable current_snapshot is either empty or does not exist.
PLAY RECAP ***************************************************************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> ansible-playbook pb.yml -e current_snapshot=''
PLAY [Evaluate false if there is anything populated in current_snapshot] *************************************************************
TASK [debug] *************************************************************************************************************************
ok: [localhost] =>
msg: The variable current_snapshot is either empty or does not exist.
PLAY RECAP ***************************************************************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> ansible-playbook pb.yml -e current_snapshot='anything'
PLAY [Evaluate false if there is anything populated in current_snapshot] *************************************************************
TASK [debug] *************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ***************************************************************************************************************************
localhost: ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
You can also use assert. For example,
shell> cat pb.yml
- name: Evaluate false if there is anything populated in current_snapshot
hosts: localhost
tasks:
- assert:
that: current_snapshot|d('', true)|length == 0
success_msg: The variable current_snapshot is either empty or does not exist.
fail_msg: The variable current_snapshot is populated.

Installing ohmyzsh for multiple users using ansible?

I want to install ohmyzsh for a list of users using gantsign.oh-my-zsh role which is available in ansible galaxy gantsign.oh-my-zsh so i have to run this role for users here is my playbook
- name: setup ohmyzsh for users
hosts: all
vars:
userlist:
- username: "viktor"
password: "viktor123"
become: yes
roles:
- role: gantsign.oh-my-zsh
users:
- username: "{{ item.username }}"
oh_my_zsh:
theme: gnzh
plugins:
- git
- zsh-syntax-highlighting
- zsh-autosuggestions
- docker
loop: "{{ userlist }}"
tasks:
but I got this error after running playbook
fatal: [worker1]: FAILED! => {"msg": "[{'username': '{{ item.username }}', 'oh_my_zsh': {'theme': 'gnzh', 'plugins': ['git', 'zsh-syntax-highlighting', 'zsh-autosuggestions', 'docker']}, 'loop': '{{ userlist }}'}]: 'item' is undefined"}
fatal: [worker2]: FAILED! => {"msg": "[{'username': '{{ item.username }}', 'oh_my_zsh': {'theme': 'gnzh', 'plugins': ['git', 'zsh-syntax-highlighting', 'zsh-autosuggestions', 'docker']}, 'loop': '{{ userlist }}'}]: 'item' is undefined"}
fatal: [registry1]: FAILED! => {"msg": "[{'username': '{{ item.username }}', 'oh_my_zsh': {'theme': 'gnzh', 'plugins': ['git', 'zsh-syntax-highlighting', 'zsh-autosuggestions', 'docker']}, 'loop': '{{ userlist }}'}]: 'item' is undefined"}
You can't use loops inside the task level roles: keywords. Use include_role inside your tasks instead (untested example):
- name: setup ohmyzsh for users
hosts: all
vars:
userlist:
- username: "viktor"
password: "viktor123"
become: yes
tasks:
- name: run role for each user
include_role:
name: gantsign.oh-my-zsh
vars:
users:
- username: "{{ item.username }}"
oh_my_zsh:
theme: gnzh
plugins:
- git
- zsh-syntax-highlighting
- zsh-autosuggestions
- docker
loop: "{{ userlist }}"
Answer for the generic looping over ansible roles using include_role.
As for the specific problem, the role expects its list of users as a role variable, so you do not need to loop over the role at all, just pass all users to the role itself, as the oh-my-zsh role docs suggest.
- hosts: all
vars:
oh_my_zsh_defaults:
theme: gnzh
plugins:
- git
- zsh-syntax-highlighting
- zsh-autosuggestions
- docker
roles:
- role: gantsign.oh-my-zsh
users:
- username: "viktor"
oh_my_zsh: oh_my_zsh_defaults
- username: "otheruser"
oh_my_zsh: oh_my_zsh_defaults

How to filter out a particular line from ansible output

I am facing an issue while running my ansible-playbook to filter out the IP address from the playbook output. Here is my code
- hosts: myhost
tasks:
- name: Gather all VMs from a specific folder
community.vmware.vmware_vm_info:
hostname: myhost.com
username: local
password: mypass
folder: "/VMFStorage/vm/"
validate_certs: False
delegate_to: localhost
register: vm_info
- debug:
var: vm_info
The output of the code
ok: [myhost] => {
"vm_info": {
"changed": false,
"failed": false,
"virtual_machines": [
{
"attributes": {},
"cluster": "Storage",
"datacenter": "VMFStorage",
"esxi_hostname": "161.92.211.24",
"folder": "/VMFStorage/vm/",
"guest_fullname": "Red Hat Enterprise Linux 8 (64-bit)",
"guest_name": "RHEL Machine",
"ip_address": "192.168.1.47",
"mac_address": [
"00:50:56:a3:b1:e9"
],
"moid": "vm-22949",
"power_state": "poweredOn",
"tags": [],
"uuid": "4223f3fa-eeae-6a96-6af5-d68dc94199f3",
"vm_network": {
"00:50:56:a3:b1:e9": {
"ipv4": [
"192.168.1.47"
],
"ipv6": [
"fe80::250:56ff:fea3:b1e9"
]
}
}
},
{
"attributes": {},
"cluster": "Storage",
"datacenter": "VMFStorage",
"esxi_hostname": "161.92.211.22",
"folder": "/VMFStorage/vm/",
"guest_fullname": "Red Hat Enterprise Linux 8 (64-bit)",
"guest_name": "Machine2",
"ip_address": "192.168.1.29",
"mac_address": [
"00:50:56:a3:b3:6d"
],
"moid": "vm-21360",
"power_state": "poweredOff",
"tags": [],
"uuid": "42239cd1-69ec-ff5d-a557-85d0bc8c9edc",
"vm_network": {
"00:50:56:a3:b3:6d": {
"ipv4": [],
"ipv6": []
}
}
},
The required output is to catch the IP address of Machine1 guest name and print that output
192.168.1.29
I tried many possibilities but couldn't find out the proper solution
Any help would be appreciated
UPDATE
- hosts: myhost
tasks:
- name: Gather all VMs from a specific folder
community.vmware.vmware_vm_info:
hostname: myhost.com
username: local
password: mypass
folder: "/VMFStorage/vm/"
validate_certs: False
delegate_to: localhost
register: vm_info
- set_fact:
ip: "{{ vm_info.virtual_machines|
selectattr('guest_name', 'eq', 'AlmaLinuxBUILD-Machine')|
map(attribute='ip_address')|first }}"
- debug:
var: ip
There are many options. For example,
Select the dictionary from the list first and extract the attribute
- debug:
var: ip
vars:
ip: "{{ vm_info.virtual_machines|
selectattr('guest_name', 'eq', 'Machine2')|
map(attribute='ip_address')|first }}"
gives
ip: 192.168.1.29
If you prefer json_query the task below gives the same result
- debug:
var: ip
vars:
ip: "{{ vm_info.virtual_machines|
json_query('[?guest_name==`Machine2`].ip_address')|first }}"
Create a dictionary first if there are many items on the list that should be referenced, e.g.
- debug:
var: name_ip
vars:
name_ip: "{{ vm_info.virtual_machines|
items2dict(key_name='guest_name',
value_name='ip_address') }}"
gives
name_ip:
Machine2: 192.168.1.29
RHEL Machine: 192.168.1.47
Then, use this dictionary to get the IP of a guest. The task below also gives the same result
- debug:
var: ip
vars:
ip: "{{ name_ip['Machine2'] }}"
Q: "How can I use the ip variable in other places in the same playbook?"
A: There are many options. For example, put it into the set_facts (precedence 19)
- set_facts:
ip: "{{ name_ip['Machine2'] }}"
- debug:
var: ip
try this.. if you only want machine1 ip_address.
- debug:
msg: "{{ vm_info.virtual_machines[1].ip_address }}"
try this.. if you only want all VMs ip_addresses.
- debug:
msg: "{{ vm_info.virtual_machines[*].ip_address }}"

Print json output in Ansible and store in a list?

Ansible playbook:
---
- task:-
- code goes here
- name: Trying to get instance Private IP from ASG
ec2_instance_facts:
instance_ids:
- "{{ item }}"
with_items: "{{ INSTANCE_IDS_FROM_ASG }}"
register: instance_ids_result
- set_fact:
msg: "{{ instance_ids_result | json_query('results[*].instances[*].network_interfaces[*].private_ip_address') }} "
- debug: var=msg
I have the output as follows:
ok: [localhost] => {
"msg": [
[
"172.31.144.74"
],
[
"172.31.147.69"
]
]
}
But, I would need the output in a list as ["172.31.144.74", "172.31.147.69"] or "172.31.147.69" "172.31.147.69".
What is the best way to print it that way?
You could flatten your list using the filter
- set_fact:
msg: "{{ instance_ids_result | json_query('results[*].instances[*].network_interfaces[*].private_ip_address') | flatten }} "

How to convert a iterate loop list into a string to use in Linux

I have Ansible role that I want to iterate over.
The goal is to create new user accounts from a list. The playbook calls the role and sends the list to iterate.
The OS (Linux Debian 8.8) sees the all of var unicode "[u'user']"
Some other tests performed show new users:
['test']
[u'test']
All I really want is to have the var to be a string so I make a new user and add the needed keys and other files. I can also join the var into paths for key and other files.
I have searched for an easy way to "| to_string", (not in Ansible)
The Filter "to_yaml" gets rid of the unicode but not "[]" and adds "\n" at the end.
The item for the ssh key copy if for the various id_(type).pub files.
I have read:
Convert Ansible variable from Unicode to ASCII
Code
Playbook:
vars_files:
- /home/admin/common/vars/UserList
gather_facts: False
roles:
- { role: common, "{{ UserList }}" }
UserList file
---
UserList:
- 'test'
...
role/common/main.yml
---
- name: Add user to server
user:
name: "{{ UserList }}"
shell: /bin/bash
- name: make direcotry
file:
path: "/home/{{ UserList }}/.ssh"
state: directory
- name: Copy ssh public key to user/.ssh/_key_.pub
copy:
src: "/home/{{ UserList }}/.ssh/{{ item }}"
dest: "/home/{{ UserList }}/.ssh/{{ item }}"
mode: 600
force: no
with_items:
- id_rsa.pub
- id_dsa.pub
- id_ecdsa.pub
...
A different form, but still errored as below.
roles:
- role: common
with_items:
- "{{ UserList }}"
Error
(item=id_rsa.pub) => {"failed": true, "invocation": {"module_args": {"dest": "/home/[u'test']/.ssh/id_rsa.pub", "force": false, "mode": 600, "src": "/home/[u'test']/.ssh/id_rsa.pub"}, "module_name": "copy"}, "item": "id_rsa.pub", "msg": "could not find src=/home/[u'test']/.ssh/id_rsa.pub"}
Workaround
I found a workarount for my issue. I would be cautious to call it a solution. For this case it will suffice. I need to "loop" with my var with the builtin {{ item }}. Then {{ item }} is used as a string and I can create the PATHs I need. I can also iterate through the a series of items with "with_nested".
- name: create empty file
file:
path: "{{ '/home/' + item + '/.ssh/authorized_keys' }}"
state: touch
with_items:
- "{{ UserList }}"
- name: Copy ssh public key to user/.ssh/_key_.pub
copy:
src: "{{ '/home/' + item[1] + '/.ssh/' + item[0] }}"
dest: "{{ '/home/' + item[1] + '/.ssh/' + item[0] }}"
mode: 600
force: no
with_nested:
- [ 'id_rsa.pub' , 'id_dsa.pub' , 'id_ecdsa.pub' ]
- "{{ UserList }}"

Resources