How to write loop in ansible with extra variable - ansible

I have got list of the mac address in the text file mac.txt such as:
[
"08f1.ea6d.033c",
"08f1.ea6d.033d",
"08f1.ea6d.033e",
"08f1.ea6d.033f",
"b883.0381.4b.20",
"b883.0381.4b21",
"b883.0384.d51c",
"b883.0384.d51d"
]
Now I want to check one by one above mac addresses in switch, to verify the connectivity of the server & switch and all these mac addresses may not be exist on the switch. Let's say only two exist and that mac needs to be stored in variable.
Note: these mac addresses may vary.
And here is the switch playbook which I was writing:
- name: Run the show lldp neighbors command & find out the switch port
ios_command:
commands: show mac address-table | in {{ macdress }}
with_item:
- macaddress
What is the correct playbook to achieve the requirement?

Since your text file is a valid json list, you simply have to read its content using the file lookup and load it inside a variable using the from_json filter. You can then use the variable (in a loop or anything else).
---
- name: Load values from json file demo
hosts: localhost
gather_facts: false
vars:
macaddresses: "{{ lookup('file', 'mac.txt') | from_json }}"
tasks:
- name: Show imported macs list
debug:
var: macaddresses
- name: Loop over imported macs
debug:
msg: "I'm looping over mac {{ item }}"
loop: "{{ macaddresses }}"
Note: loop is the recent syntax for looping and is the equivalent of with_list. In this situation you can perfectly replace it with with_list or with_item which are not deprecated and will do the same job. See the ansible loop documentation for more info.

Related

Extracting last IPv4 octet value for each host in Ansible without extra debug info

I'm trying to get the 4th IPv4 octet for each host in Ansible.
However, I only get it with extra debug info.
I would like to get only the value.
An example IP of one host: 172.27.0.4
Current output for example:
{"msg": "4", "failed": false, "changed": false}
The output I would like to get is:
4
My current playbook:
---
- hosts: all
vars:
ip: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}"
tasks:
- name: print 4th octet
debug:
msg: "{{ ip.split('.')[3] }}"
register: ip1
- name: write 4th octet to file
copy: content="{{ ip1 }}" dest=/root/ip.txt
The output is written to file to test it.
You can do all this in a single task.
The debug task is not necessary, as well as the definition of the varaiable ip.
---
- hosts: all
tasks:
- name: write 4th octet to file
copy: content="{{ (ansible_default_ipv4.address | split('.')).3 }}" dest=/root/ip.txt
For clarity, you can define a varaiable within the task that is only valid during task execution.
I would prefer this variant.
- hosts: all
tasks:
- name: write 4th octet to file
copy:
dest: /root/ip.txt
content: "{{ ip }}"
vars:
ip: "{{ (ansible_default_ipv4.address | split('.')).3 }}"
What you are doing at the moment is the whole thing in three steps:
Define variable ip and store the value of another variable in it.
perform an operation and output it (via debug), at the same time register the output
use the value registered by the output to write it to a file
This can be summarized as described above.
The output you mention is that of the debug task.
{"msg": "4", "failed": false, "changed": false}
Since you registered the output in the variable ip1, you could access the actual output value via ip1.msg. But this is not necessary, because for your case the debug task is not needed at this point.
more detailed description for accessing the default IP address
You use hostvars[inventory_hostname]['ansible_default_ipv4'] to access the IP address, more precisely the variable ansible_default_ipv4 of the current host, because the variable inventory_hostname returns you the host of the system on which the play is currently running. You can access this variable simply by using ansible_default_ipv4, you will get the address via:
ansible_default_ipv4.address or
ansible_default_ipv4['address'].
Both variants are the same in this case.
So you can use this as a basis for the split of the dots.
Splitting is possible with the filter split(), so you can do the splitting with the following code:
ansible_default_ipv4.address | split('.')
You can then address the 4th element with either .3 or the equivalent [3].
If you want to get this for all hosts in the play in one go, you can extract the ip fact from each hosts, map the split function and retain only the fourth element for each result.
Some references to understand the playbook below:
Check the ansible_play_hosts magic variable definition
See ansible documentation for map('extract', ....)
See the global documentation for the jinja2 map filter
---
- hosts: all
vars:
my_last_ips_digits: "{{
ansible_play_hosts |
map('extract', hostvars, ['ansible_default_ipv4', 'address']) |
map('split', '.') | map(attribute=3)
}}"
tasks:
- name: Print last ip digits for all hosts
debug:
var: my_last_ips_digits
run_once: true
Of course, you can reuse that list variable anywhere you like besides a debug output.

Ansible: How to copy output of 4 commands

I would like to copy the content of the variable showoutput (in which there are 4 commands) in a file. But, when I execute my playbook, It copy only the first one command.
How to copy the four commands ?
Below my playbook:
- hosts: ios
vars:
command_list:
- show cdp neigh
- show ip interface brief
- show clock
- show arp
tasks:
- name: Run the SHOW commands and save output
ios_command:
commands: "{{ command_list }}"
register: showoutput
- name : Copy the result in a file
copy:
content: "{{showoutput.stdout[0]}}"
dest: "/home/net/output/{{hostvars.localhost.DTG}}/{{inventory_hostname}}-{{hostvars.localhost.DTG}}-test.txt"
Ansible's documentation on Registering variables,
When you register a variable in a task with a loop, the registered
variable contains a value for each item in the loop. The data
structure placed in the variable during the loop will contain a
results attribute, that is a list of all responses from the module.
The output of each command should be available in showoutput.results which is an array. You can loop over them as
- name : Copy the result in a file
copy:
content: "{{ item.stdout }}"
dest: "/home/net/output/{{hostvars.localhost.DTG}}/{{inventory_hostname}}-{{hostvars.localhost.DTG}}-test.txt"
with_items: "{{ showoutput.results }}"

How to show all the host names of hosts within a specific OS in Ansible

Is there any command in Ansible to collect the hostnames of hosts with OS Debain?
The file hosts contains no groups!
So a simple command to see the hostnames of hosts containing Debain.
The setup module, which is implicitly called in any playbook via the gather_facts mechanism, contains some output that you could use.
Look for example for the ansible_distribution fact, which should contain Debian on the hosts you are looking for.
If all you want is that list once, you could invoke the module directly, using the ansible command and grep:
ansible all -m setup -a 'filter=ansible_distribution' | grep Debian
If you want to use that information dynamically in a playbook, you could use this pattern:
---
- hosts: all
tasks:
- name: Do something on Debian
debug:
msg: I'm a Debian host
when: ansible_distribution == 'Debian'
I would add that if you want to target distribution and minimum version (often the case), you could do that with a dictionary. I often use this snippet:
---
- hosts: localhost
vars:
minimum_required_version:
CentOS: 7
Debian: 11
Fedora: 31
RHEL7: 7
Ubuntu: 21.10
- name: set boolean fact has_minimum_required_version
set_fact:
has_minimum_required_version: "{{ ansible_distribution_version is version(minimum_required_version[ansible_distribution], '>=') | bool }}"
tasks:
- name: run task when host has minimum version required
debug:
msg: "{{ ansible_distribution }} {{ ansible_distribution_version }} >= {{ minimum_required_version[ansible_distribution] }}"
when: has_minimum_required_version
You could also dynamically include a playbook fragment that targets specific distributions or versions like so:
- name: Include task list in play for specific distros
include_tasks: "{{ ansible_distribution }}.yaml"
Which assumes you'd have Debian.yaml, Ubuntu.yaml, etc. defined for each distro you want to support.

Extract nslookup result

I'm trying to figure out how I can extract the value of the "Address" of a nslookup command result in an Ansible task. I will always get back 1 ip address in the result:
nslookup fs-d12345.efs.ap-southeast-2.amazonaws.com 10.75.0.2
Server: 10.75.0.2
Address: 10.75.0.2#53
Non-authoritative answer:
Name: fs-d12345.efs.ap-southeast-2.amazonaws.com
Address: 10.75.21.67
I need the value 10.75.21.67 to be stored as a var that I can use later in the playbook.
My task would look something like:
- shell: "nslookup fs-d12345.efs.ap-southeast-2.amazonaws.com 10.75.0.2"
register: results
How do I extract the value of the Address?
Thanks!
Before reaching for the command or shell module, always have a look around for alternatives as for common tasks, the Ansible community have often done the heavy lifting for you.
If you can accommodate installing an additional Python package to support, there is already an nslookup (well dig) utility built into Ansible:
- set_fact:
target_ip: "{{ lookup('dig', 'fs-d12345.efs.ap-southeast-2.amazonaws.com', '#10.75.0.2') }}"
The pre-requisite to getting this to work, is you need the dnspython library installed on the machine where this task will be run, e.g.
apt-get install python-dnspython
or
yum install python-dns # (I think ...)
If you only want to have to do this on your control machine, but you want to be able to access the looked up data on a remote machine, you could do something like this:
- hosts: localhost
connection: local
tasks:
- set_fact:
target_ip: "{{ lookup('dig', 'fs-d12345.efs.ap-southeast-2.amazonaws.com', '#10.75.0.2') }}"
- hosts: remote_machine
tasks:
- debug:
var: hostvars['localhost'].target_ip

How do I loop over each line inside a file with ansible?

I am looking for something that would be similar to with_items: but that would get the list of items from a file instead of having to include it in the playbook file.
How can I do this in ansible?
I managed to find an easy alternative:
- debug: msg="{{item}}"
with_lines: cat files/branches.txt
Latest Ansible recommends loop instead of with_something. It can be used in combination with lookup and splitlines(), as Ikar Pohorský pointed out:
- debug: msg="{{item}}"
loop: "{{ lookup('file', 'files/branches.txt').splitlines() }}"
files/branches.txt should be relative to the playbook
Lets say you have a file like
item 1
item 2
item 3
And you want to install these items. Simply get the file contents to a variable using register. And use this variable for with_items. Make sure your file has one item per line.
---
- hosts: your-host
remote_user: your-remote_user
tasks:
- name: get the file contents
command: cat /path/to/your/file
register: my_items
- name: install these items
pip: name:{{item}}
with_items: my_items.stdout_lines
I am surprised that nobody mentioned the ansible Lookups, I think that is exactly what you want.
It reads contents that you want to use in your playbook but do not want to include inside the playbook from files, pipe, csv, redis etc from your local control machine(not from remote machine, that is important, since in most cases, these contents are alongside your playbook on your local machine), and it works with ansible loops.
---
- hosts: localhost
gather_facts: no
tasks:
- name: Loop over lines in a file
debug:
var: item
with_lines: cat "./files/lines"
with_lines here is actually loop with lines lookup, to see how the lines lookup works, see the code here, it just runs any commands you give it(so you can give it any thing like echo, cat etc), then split the output into lines and return them.
There are many powerful lookups, to get the comprehensive list, check out the lookup plugins folder.

Resources