Ansible jinja dictionary create multiple sudo files for each user - linux

I'm trying to create a sudo file for each user.
Playbook:
- name:
hosts: all
gather_facts: false
tasks:
- name:
template:
src: sudo.j2
dest: "/etc/sudoers.d/{{item.name}}"
loop: "{{userinfo}}"
when: "'admins' in item.groupname"
Var file:
userinfo:
- groupname: admins
name: bill
- groupname: admins
name: bob
- groupname: devs
name: bea
Jinja file:
{% for item in userinfo %}
{% if item.groupname=="admins" %}
{{item.name}} ALL=ALL NOPASSWD:ALL
{% endif %}
{% endfor %}
What I am getting is two files but with information of both users.
bill ALL=ALL NOPASSWD:ALL
bob ALL=ALL NOPASSWD:ALL
How do I make it work such that each file contains information of that user only

The issue is that you have 2 loops: one in the playbook, the other in the template jinja file; try leaving the template file with the templated information only
{{ item.name }} ALL=ALL NOPASSWD:ALL

Related

Ansible: Playbook to export systemd status into CSV format

We have more than 1000+ VMs running on different Hyper-V nodes. I want to create a report in CSV format for one systemd service status.
For example, I would like to check the running status of postfix whether it's in state started or stopped. These statuses need to be print into CSV file format.
Expected result as below format
Finally, I got solution for this above request.
Here is the code below, helpful for others
Thanks to the gregsowell blog helped me get these done. https://gregsowell.com/?p=7289
---
- name: Generate an HTML report from jinja template
hosts: postfix-hosts
gather_facts: true
vars:
#email settings
email_subject: System status Report
email_host: stackoverflw.smtp.com
email_from: noreply#stackoverflw.com
email_to: AdMin_Stack#stackoverflw.com
#random settings
csv_path: /tmp
csv_filename: report.csv
headers: Hostname,OS,Distro Ver,Kernel Ver,Postfix Status,FQDN,Total VCPU,Total RAM,Total SWAP,Total Disk,Hyper-V
tasks:
- name: Gather last Postfix Status
ansible.builtin.shell: systemctl status postfix | egrep -i Active | awk '{ print $2,$3}'
register: active
- name: Save CSV headers
ansible.builtin.lineinfile:
dest: "{{ csv_path }}/{{ csv_filename }}"
line: "{{ headers }}"
create: true
state: present
delegate_to: localhost
run_once: true
- name: Build out CSV file
ansible.builtin.lineinfile:
dest: "{{ csv_path }}/{{ csv_filename }}"
line: "{{ inventory_hostname }},{{ ansible_distribution }},{{ ansible_distribution_version }},{{ ansible_kernel }},{{ active.stdout }},{{ ansible_fqdn }},{{ ansible_processor_vcpus }},{{ ansible_memtotal_mb }},{{ ansible_swaptotal_mb }},{{ ansible_devices.vda.partitions.vda1.size }},{{ ansible_product_name }}"
create: true
state: present
delegate_to: localhost
- name: Read in CSV to variable
community.general.read_csv:
path: "{{ csv_path }}/{{ csv_filename }}"
register: csv_file
delegate_to: localhost
run_once: true
# - name: debug csv_file
# debug:
# var: csv_file
# run_once: true
- name: Send Email
community.general.mail:
host: "{{ email_host }}"
from: "{{ email_from }}"
port: 25
to: "{{ email_to }}"
subject: "[Ansible] {{ email_subject }}"
body: "{{ lookup('template', 'report.html.j2') }}"
attach: "{{ csv_path }}/{{ csv_filename }}"
subtype: html
delegate_to: localhost
run_once: true
report.html.j2
<table style="border: 1px solid black; border-collapse: collapse;">
<tr>
{% for header in headers.split(",") %}
<th style="border: 1px solid black; padding: 8px 16px;">{{ header }}</th>
{% endfor %}
</tr>
{% for host in csv_file.list %}
<tr>
{% for header in headers.split(",") %}
<td style="border: 1px solid black; padding: 8px 16px;">{{ host[header] }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>

If condition does not work in state triggered by reactor

I am using an if condition utilizing grain item within a state which triggered by reactor.
and I got an error message Jinja variable 'dict object' has no attribute 'environment'
=================================================
REACTOR config:
cat /etc/salt/master.d/reactor.conf
reactor:
- 'my/custom/event':
- salt://reactor/test.sls
==============================
test.sls
cat /srv/salt/reactor/test.sls
sync_grains:
local.saltutil.sync_grains:
- tgt: {{ data['id'] }}
{% if grains['environment'] in ["prod", "dev", "migr"] %}
test_if_this_works:
local.state.apply:
- tgt: {{ data['id'] }}
- arg:
- dummy_state
{% endif %}
===================================
dummy_state/init.sls
cat /srv/salt/dummy_state/init.sls
create_a_directory:
file.directory:
- name: /tmp/my_test_dir
- user: root
- group: root
- makedirs: True
=================================================
salt 'salt-redhat-23.test.local' grains.item environment
salt-redhat-23.test.local:
----------
environment:
prod
=================================================
salt-redhat-23 ~]# cat /etc/salt/grains
role: MyServer
environment: prod
================================================
If I change the test.sls and use instead of custom grain a grain which salt-master is taking by default then it will works. Also it will work without the if condition in the state.
Do you know why this is happening?
Thank you all in advance.
Issue resolved.
You cannot use custom grains with Reactor directly, you need to call another state to be able to add condition there.
for instance:
cat /etc/salt/master.d/reactor.conf
reactor:
- 'my/custom/event':
- salt://reactor/test.sls
test.sls
# run a state using reactor
test_if_this_works:
local.state.apply:
- tgt: {{ data['id'] }}
- arg:
- reactor.execute
execute.sls
{% set tst = grains['environment'] %}
{% if tst in ['prod', 'dev', 'test', 'migr'] %}
create_a_directory:
file.directory:
- name: /tmp/my_test_dir
- user: root
- group: root
- makedirs: True
{% endif %}
this will work with the if condition, if you try to add the if statement on the test.sls it will not work.

Ansible playbook loop with with_items

I have to update sudoers.d multiple user files with few lines/commands using ansible playbook
users.yml
user1:
- Line1111
- Line2222
- Line3333
user2:
- Line4444
- Line5555
- Line6666
main.yml
- hosts: "{{ host_group }}"
vars_files:
- ../users.yml
tasks:
- name: Add user "user1" to sudoers.d
lineinfile:
path: /etc/sudoers.d/user1
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ user1 }}"
The above one is working only for user1..
If I want to also include user2 --> How to change the file name : path: /etc/sudoers.d/user1
I tried below and its not working :
Passing below users as variable to main.yml while running
users:
- "user1"
- "user2"
- name: Add user "{{users}}" to sudoers.d
lineinfile:
path: /etc/sudoers.d/{{users}}
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
So, basically I want to pass in users to a variable {{users}} as user1 and user2 and wanted to use the lines for each user from users.yml and add it to respective user files (/etc/sudoers.d/user1 and /etc/sudoers.d/user2).
So /etc/sudoers.d/user1 should look like
Line1111
Line2222
Line3333
and /etc/sudoers.d/user2 should look like
Line4444
Line5555
Line6666
Try to add quotes:
users:
- "user1"
- "user2"
- name: "Add user {{users}} to sudoers.d"
lineinfile:
path: "/etc/sudoers.d/{{users}}"
line: "{{ item }}"
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
As per Ansible Documentation on Using Variables:
YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be sure you aren’t trying to start a YAML dictionary. This is covered on the YAML Syntax documentation.
This won’t work:
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
Do it like this and you’ll be fine:
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
cat users.yml
---
users:
- user1:
filename: user1sudoers
args:
- Line1111
- Line2222
- Line3333
- user2:
filename: user2sudoers
args:
- Line4444
- Line5555
- Line6666
I use template here, instead of lineinfile
---
cat sudoers.j2
{% if item.args is defined and item.args %}
{% for arg in item.args %}
{{ arg }}
{% endfor %}
{% endif %}
the task content
---
- hosts: localhost
vars_files: ./users.yml
tasks:
- name: sync sudoers.j2 to localhost
template:
src: sudoers.j2
dest: "/tmp/{{ item.filename }}"
loop: "{{ users_list }}"
when: "users_list is defined and users_list"
after run the task.yml, generate two files under /tmp directory.
cat /tmp/user1sudoers
Line1111
Line2222
Line3333
cat /tmp/user2sudoers
Line4444
Line5555
Line6666

Ansible templates with undefined variables

I have a file with variables that I use in my playbook:
net_interfaces:
...
- name: "eth0"
ip: "192.168.1.100"
mask: "255.255.255.0"
gateway: "192.168.1.1"
...
and I want to deploy some configs with this variables, for example ifcfg-eth0:
DEVICE={{ item.name }}
TYPE=Ethernet
ONBOOT=yes
BOOTPROTO=static
IPADDR={{ item.ip }}
NETMASK={{ item.netmask }}
GATEWAY={{ item.gateway }}
but sometimes there is no gateway variable for item and in this case I want to remove string
GATEWAY={{ item.gateway }}
from this config file on the target machine. How can I achieve this without creating another task for a certain hosts?
Add condition:
{% if item.gateway is defined %}
GATEWAY={{ item.gateway }}
{% endif %}
Another (and better) way is to use 'default' filter because in this case we can check if some variable was defined and set it's default value if it wasn't. Example:
{{ my_string_value | default("awesome") }}

How to set default user for mounted folder?

When I put in top.sls this:
/var/www:
file.directory:
- user: {{ pillar['user'] }}
- group: www-data
- mode: 755
- makedirs: True
It creates "/var/www" dir with permissions which are defined and that is ok.
So basically chown is: user:www-data
But when I try to mount that folder to my Mac then problem show up.
owner and group are-> 501:dialout
Here is code which I use:
/var/www:
{% if pillar['sshfs_www'] %}
file.directory:
- mode: 755
- follow_symlinks: False
- group: www-data
- makedirs: True
mount:
- user: {{ pillar['user'] }}
- mounted
- device: sshfs#{{ pillar['sshfs_www'] }}
- fstype: fuse
- opts: nonempty,allow_other,auto
{% else %}
file.directory:
- mode: 755
- group: www-data
- makedirs: True
{% endif %}
Not only that user and group are not set as I set, I get error: Failed to change user to myuser
How can I mount with my user and group?
Thank you
I hope this will help other users to solve their problem with permissions when mounting with salt:
So here how I solved that.
First I manually setup id for user and group:
{{ pillar['user'] }}:
user.present:
- shell: /bin/bash
- home: /home/{{ pillar['user'] }}
- require_in:
- uid: 4000
- gid: 4000
- file: /home/{{ pillar['user'] }}/.ssh/id_rsa
- file: /home/{{ pillar['user'] }}/.ssh/authorized_keys
www-data:
group.present:
- gid: 4000
- system: True
- members:
- {{ pillar['user'] }}
After that in part where is mount, I defined uid and gid with this part: uid=4000,gid=4000
/var/www:
{% if pillar['sshfs_www'] %}
mount:
- user: {{ pillar['user'] }}
- mounted
- device: sshfs#{{ pillar['sshfs_www'] }}
- fstype: fuse
- opts: nonempty,allow_other,auto,uid=4000,gid=4000
{% else %}
file.directory:
- mode: 755
- group: www-data
- makedirs: True
{% endif %}
Citing Sven from a serverfail answer:
You can't. That's a limitation of SSHFS/Fuse: Everything is mapped to the permission of the user you use to connect with SSH by default.
However, it appears you can work around this a bit with idmap files, see the options -o idmap, -o uidfile, -o gidfile and -o nomap in the man page.

Resources