can I run a shell command in jinja2 template in ansible - ansible

This is a playbook that connects with all the servers in my inventory file, and makes a note of the server ip and mount point information of hosts where mount point usage exceeds 80% and it writes to a text file on the localhost (ansible-controller).
- hosts: all
tasks:
- shell:
cmd: df -h | sed 's/%//g' | awk '$5 > 80 {if (NR > 1) print $5"%",$6}'
register: disk_stat
- debug:
var: disk_stat
- file:
path: /home/app/space_report_{{ td }}.txt
state: touch
run_once: true
delegate_to: localhost
- shell: echo -e "{{ ansible_host }} '\n' {{ disk_stat.stdout_lines| to_nice_yaml }}" >> /home/thor/space_report_{{ td }}.txt
args:
executable: /bin/bash
delegate_to: localhost
I was wondering if I could create a jinja2 template and bring the playbook down to one task. I am stuck at integrating a shell command inside the jinja2 template and I am not sure if it is possible. Please advise.
- hosts: all
tasks:
- template:
src: monitor.txt.j2
dest: /home/app/playbooks/monitor.txt
delegate_to: localhost
monitor.txt.j2
{% for host in groups['all'] %}
{{ hostvars[host].ansible_host }}
--shell command--
{% endfor %}

As I say in my comment under your question, while it is possible to use shell or command modules, Ansible is a configuration / automation tool, so it's better to forget your shell coding / logic to use native ansible functionalities, that'll ease the tasks / playbook writing.
For instance, it's no needed to do a df because ansible when connecting will gather facts about the target, including devices and their capacity and current usage so you can use that directly.
For the jinja question, you can use the module copy and pass directly jinja code in the option content on this module:
- name: Trigger a tasks on hosts to gather facts
hosts: all
tasks:
- copy:
dest: /home/app/playbooks/monitor.txt
content: |
{% for host in groups['all'] %}
{% for dev in hostvars[host].ansible_mounts %}
{% if (dev.block_used / dev.block_total * 100 ) > 80 %} {{ dev.block_used / dev.block_total * 100 }} {{ dev.mount }} {% endif %}
{% endfor %}
{% endfor %}
run_once: true
delegate_to: localhost

Related

Ansible run shell module on multiple host's and redirect output to 1 file

I need to run shell module on all hosts group and copy the register variable to a file on any server.
NOTE : I don't want to copy the results in my local i need it on server
- name: date.
shell: cat /ngs/app/user/test
register: date_res
changed_when: false
- debug:
msg: "{{ ansible_play_hosts | map('extract', hostvars, 'date_res') | map(attribute='stdout') | list }}"
run_once: yes
- name: copy bulk output
copy:
content: "{{ allhost_out.stdout }}"
dest: "/ngs/app/{{ app_user }}/test"
Jinja2 loop can be helpful for your case.
Tested on ansible [core 2.13.3]
- name: date.
shell: cat /ngs/app/user/test
register: date_res
changed_when: false
- name: copy bulk output
copy:
content: |
{% for host in vars['play_hosts'] %}
{{ host }} {{ hostvars[host].date_res.stdout }}
{% endfor %}
dest: "/ngs/app/{{ app_user }}/bldall"
when: inventory_hostname == host-001.example.com
On host-001.example.com placed a file contains: host1 file content.
On host-002.example.com placed a file contains: host2 file content.
Output on host specified in the copy task:
host-001.example.com host1 file content
host-002.example.com host2 file content

ansible variables usage - ansible_hostname or {{ ansible_hostname }}

I am new to ansible. What is the correct to call ansible variables? Here are the 3 playbooks, playbook 1 uses "{{ ansible_hostname }}", however, playbook 2 and 3 uses "ansible_hostname" directly. What are the differences? Thanks!
Playbook 1:
tasks:
- name: Jinja2 template
template:
src: template.j2
dest: "/tmp/{{ ansible_hostname }}_template.out"
trim_blocks: true
mode: 0644
Playbook 2:
tasks:
- name: Ansible Jinja2 if
debug:
msg: >
--== Ansible Jinja2 if statement ==--
{# If the hostname is ubuntu-c, include a message -#}
{% if ansible_hostname == "ubuntu-c" -%}
This is ubuntu-c
{% endif %}
Playbook 3:
tasks:
- name: Exploring register
command: hostname -s
when:
- ansible_distribution == "CentOS"
- ansible_distribution_major_version | int >= 8
register: command_register
playbook 1 uses "{{ ansible_hostname }}", however, playbook 2 uses "ansible_hostname"
That's not entirely correct. Both playbooks use the variable name ansible_hostname inside a Jinja templating context.
In the first playbook, it's simple variable substitution, so we use the {{ ... }} markers.
In the second playbook, it's being used in a control expression, so we use the {% ... %} markers.
In the third playbook, you're looking at the clauses of a when expression. From the documentation:
The when clause is a raw Jinja2 expression without double curly braces...
You can read more about Jinja syntax here.

Add lines to a local file from a Jinja2 Template with Ansible

im a newbie to ansible and got problems with this Task.
I want to get some data from various hosts. The Idea is, to use a jinja2 template, get the data from the host and add this data in to a file local on the Ansible machine.
How is it possible to get all the data in one file on the local machine? The way i try to do it brings me the result from just one host. Thanks for your Help!
---
- name: "Server Report"
hosts: all
tasks:
- name: "check packages"
package_facts:
manager: auto
- name: "get PHP info"
shell: "php -v | grep -E ^PHP | awk '{print $2}'"
register: php_version
when: "'php-common' in ansible_facts.packages"
- name: "get MySQL info"
shell: "mysql -V | awk '{print $5}' | sed 's/,//g'"
register: mysql_version
when: "'mysql-common' in ansible_facts.packages"
- name: "Use Template to create File"
template:
src: vrsn.j2
dest: /opt/data.txt
delegate_to: localhost
This is the Jinjer2 Code:
{% if ansible_facts['hostname'] is defined %}{{ ansible_facts['hostname'] }},{% else %}NoINstalled,{% endif %}
{% if ansible_facts['distribution'] is defined %}{{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }},{% else %}NotInstalled,{% endif %}
{% if php_version.stdout is defined %}{{ php_version.stdout }},{% else %}NotInstalled,{% endif %}
{% if mysql_version.stdout is defined %}{{ mysql_version.stdout }},{% else %}NotInstalled,{% endif %}
{% if ansible_facts.packages['apache2'][0].version is defined %}
apache2-version {{ ansible_facts.packages['apache2'][0].version }},
{% elif ansible_facts.packages['apache'][0].version is defined %}
apache-version {{ ansible_facts.packages['apache'][0].version }},
{% elif ansible_facts.packages['nginx-common'][0].version is defined %}
nginx-version {{ ansible_facts.packages['nginx-common'][0].version }},
{% else %}NotInstalled{% endif %}
I'm not sure I fully understand your output format, but the following example should give you a clue to go on:
vrsn.j2
{% for h in groups['all'] %}
Inventory host: {{ h }}
hostname: {{ hostvars[h].ansible_hostname | default('N/A') }}
distribution: {{ hostvars[h].ansible_distribution | default('N/A') }}
php: {{ hostvars[h].php_version.stdout | default('N/A') }}
# add more here now you got the concept
---
{% endfor %}
And you should call your template like so in your play:
- name: "Dump all host info to local machine"
template:
src: vrsn.j2
dest: /opt/data.txt
delegate_to: localhost
run_once: true

How to reuse an Ansible variable inside jinja2 template?

I've got the following:
Ansible playbook:
-
hosts: all
gather_facts: true
#remote_user: ansible
tasks:
-
name: "check dir size"
shell: "du -sh /tmp/service/log"
register: size
-
debug:
msg: "{{ size.stdout }}"
-
template:
src: template2.j2
dest: ./file.out
delegate_to: localhost
run_once: true
template2.j2
{% for i in groups['all'] %}
{{ loop.index0 }} {{ hostvars[i].inventory_hostname }} {{ size.stdout }}
{% endfor %}
Result:
$ cat file.out
0 host00 1.7G /tmp/service/log
1 host11 1.7G /tmp/service/log
2 host12 1.7G /tmp/service/log
In the output file the directory size remains constant.
How can I check the size of a directory for each host in the jinja2 loop?
template2.j2 is being templated at localhost (because of delegate_to: localhost), so size is for localhost.
You might want to use hostvars[i].size.stdout, as you use for inventory_hostname.

Ansible Jinja2 template for loop

I have two linux servers:
- server1: ip: 10.241.55.6, hostname: server1
- server2: ip: 10.242.55.7, hostname: server2
I have created an ansible inventory file named servers with the content bellow:
[IC]
10.241.55.6
10.241.55.7
Now I have created this jinja2 inventory template file: test.j2 with this content:
[IC]
{% for hostip in groups['IC'] %}
{% if hostip == ansible_default_ipv4.address %}
{{ ansible_default_ipv4.address }} default_hostname={{ ansible_nodename }}
{{ ansible_default_ipv4.address }} default_hostname={{ ansible_nodename }}
{% endif %}
{% endfor %}
And I'm running this ansible playbook:
---
- name: Generate portal inventory file
hosts: all
tasks:
- name: Generate inventory
delegate_to: localhost
template:
src: inventory/test.j2
dest: inventory/test
The command is: ansible-playbook -i inventory/servers generate-inventory.yml
The final goal is that ansible connects to each of the servers from the inventory files and then based on the jinja2 inventory template, it creates a new inventory file with this format:
[IC]
10.241.55.6 default_hostname=hostname_of_the_server_with_that_ip
and so on...
The issue here with the for loop is that all the entries are with the same server ip (while I should have an entry for each of the servers with their respective hostnames):
[IC]
10.241.55.6 default_hostname=server1
10.241.55.6 default_hostname=server2
What I'm missing here? Also if there is any other better way to achieve this please let me know.
You're using the same variable twice in the template...
{{ ansible_default_ipv4.address }} default_hostname={{ ansible_nodename }}
{{ ansible_default_ipv4.address }} default_hostname={{ ansible_nodename }}
...so of course you're getting two identical lines. It sounds like you want to access the per-host value of this variable, which means you need to access it via hostvars.
Maybe something like this:
[IC]
{% for host in groups['IC'] %}
{{ hostvars[host].ansible_default_ipv4.address }} default_hostname={{ hostvars[host].ansible_nodename }}
{% endfor %}

Resources