How do I access values of Ansible Facts when using a loop? - ansible

This works when I don't use a loop:
---
- hosts: localhost
tasks:
- name: Get a cert from gRPC port
get_certificate:
host: "labrouter.abc.com"
port: 50051
delegate_to: localhost
run_once: true
ignore_errors: yes
register: cert
- name: Get Cert Data
debug:
msg: "Not_After: {{ cert.not_after }}"
Output:
TASK [Get Cert Data] ****************************************
ok: [localhost] => {
"msg": "Not After: 20221210143235Z"
}
However, when I try to loop through my hosts like this:
---
- hosts: localhost
tasks:
- name: Get a cert from gRPC port
get_certificate:
host: "{{ item }}"
port: 50051
delegate_to: localhost
run_once: true
ignore_errors: yes
register: cert
loop: "{{ groups['lab'] }}"
- name: Get Cert Data
debug:
msg: "Not After: {{ cert.not_after }}"
I get this error:
The error was: 'dict object' has no attribute 'not_after'
My inventory file contains a single group called "lab" with one device "labrouter.abc.com" associated to it.
I want to be able to add more devices to the "lab" group and run this playbook so I can get the "not_after" value for each device. I can't figure out how to access the "not_after" value for each item in the loop.

Quoting the documentation:
When you use register with a loop, the data structure placed in the variable will contain a results attribute that is a list of all responses from the module. This differs from the data structure returned when using register without a loop
The easiest way to fix your actual problem is to loop over the results:
- name: Get Cert Data
debug:
msg: "Not After: {{ item.not_after }}"
loop: "{{ cert.results }}"
Although this is a direct answer to your question, if the final goal is to take actions on each target in the lab group, see #Jack's answer and drop your loop.

Stop looping, and have Ansible do its implicit looping over the hosts:
---
- hosts: lab
tasks:
- name: Get a cert from gRPC port
get_certificate:
host: "{{ ansible_host }}"
port: 50051
delegate_to: localhost
ignore_errors: yes
register: cert
- name: Get Cert Data
debug:
msg: "Not After: {{ cert.not_after }}"
(As a general "best practices" thing, if you are looping over hosts, there's probably a better way to do it by setting the hosts value for the play to those hosts, and letting Ansible do its implicit looping.)

Related

Ansible hostvars variable undefined when debugging/templating etc

I'm trying to dynamically generate part of an /etc/hosts file with ansible, by gathering facts from all the hosts and looping through the resulting hostvars to grab the IP of the second interface (I need a private IP on this interface, rather than the public IP of the other interface)
I can grab this information from a single host using the following plays:
- name: play 1
hosts: all
- name: play 2
hosts: localhost
connection: local
become: no
tasks:
- debug:
var: hostvars['mysinglehost'].ansible_all_ipv4_addresses[1]
...but what I'd like to do is loop through all the hosts and get this value from every host, eventually writing this information to my hosts file, but I'd settle for just getting some debug information :)
I tried
...
- debug:
var: hostvars[item].ansible_all_ipv4_addresses[1]
with_inventory_hostnames:
- all
...
...which gives the output I expect, yet when I try to output a msg with this debug task:
...
- debug:
msg: "{{ hostvars[item].ansible_all_ipv4_addresses[1] }}"
with_inventory_hostnames:
- all
...
I get the following error (as I do when I attempt to e.g. write to a file using lineinfile):
fatal: [localhost]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'ansible_all_ipv4_addresses'
I'm not sure why this is happening, as it's trying to reference the exact same variable. Is there a way to do this?
I've not tested this, but in principle, perhaps this would work?
- hosts: all
- hosts: localhost
gather_facts: false
tasks:
- set_fact:
my_hosts: |
{
{% for a_host in hostvars | dict2items %}
"{{ a_host.key }}": "{{ a_host.value.ansible_all_ipv4_addresses[1] }}",
{% endfor %}
}
Then, instead of looping over hostvars, you just loop over my_hosts?

Ansible Cisco configuration compliance check for invalid users

I am attempting to validate a Cisco configuration with Ansible. I want to be able to tell whether any users have been configured other than the valid ones.
Valid users:
username admin,
username readonly
Invalid users:
username secretbackdoor
I have tried to create a list of users, then flag any which are not valid. The code i have so far is as follows:
---
- hosts: cisco
gather_facts: no
tasks:
- name: show run
ios_command:
commands:
- show run
register: cisco_show_run
- name: list_cisco_usernames
set_fact: cisco_usernames="{{ cisco_show_run.stdout[0] | regex_findall('username (\S+)', multiline=True) }}"
- name: print usernames
debug:
msg: {{ item }}
with_items: "{{ cisco_usernames }}"
This will print out the three users. Not sure where to go next.
"Set Theory Filters" might be next option. For example
- hosts: localhost
vars:
valid_users: [admin, readonly]
invalid_users: [secretbackdoor]
cisco_usernames: [admin, readonly, secretbackdoor]
tasks:
- name: Display users not in valid_users
debug:
msg: Not among valid users {{ not_valid }}
when: not_valid|length > 0
vars:
not_valid: "{{ cisco_usernames|difference(valid_users) }}"
- name: Display users in invalid_users
debug:
msg: Among invalid users {{ not_valid }}
when: not_valid|length > 0
vars:
not_valid: "{{ cisco_usernames|intersect(invalid_users) }}"
gives (abridged)
ok: [localhost] =>
msg: Not among valid users ['secretbackdoor']
ok: [localhost] =>
msg: Among invalid users ['secretbackdoor']
Thanks for this. Your solution is working fine. I put in the first option, as I do not always know what the 'incorrect' users are.

access a variable in a variable

I am 90% sure this doesn't work because i'm doing it the wrong way, but i can't figure out what is the "right way",I hope you can get my point :
I am trying to access an ipv4 of a certain interface,I have in my hosts file interface_lan = enp4s0 because i need it in a role,so i thought i might just use it to have the IP address of that interface :
"{{hostvars[inventory_hostname]['ansible_{{interface_lan}']['ipv4.address']}}"
with that command,he is looking for "ansible_{{interface_lan}}" but i want him to look for
"ansible_"{{interface_lan}}"" and to consider "{{interface_lan}}" as a variable,not as a string.
I tried my best to explain,sorry if you did not understand you are free to enjoy the rest of your day without helping me, i have been ignoring this line for a few days now.
thank you !
Concatenate the name of the attribute an use it in the index. For example
- hosts: localhost
gather_facts: false
vars:
interface_lan: enp4s0
ansible_enp4s0:
ipv4:
address: 10.1.0.10
tasks:
- set_fact:
ansible_enp4s0: "{{ ansible_enp4s0 }}"
- debug:
msg: "{{ hostvars[inventory_hostname][my_ifc]['ipv4']['address'] }}"
vars:
my_ifc: "{{ 'ansible_' ~ interface_lan }}"
gives (abridged)
msg: 10.1.0.10
Note: set_fact is needed in the example to put the dictionary ansible_enp4s0 into the hostvars.
The indirect addressing of variables without hostvars is possible with the lookup plugin vars. For example
- hosts: localhost
vars:
test_eth0: 10.1.0.10
test_eth1: 10.1.0.11
tasks:
- debug:
msg: "{{ item }}: {{ lookup('vars', 'test_' ~ item ) }}"
loop:
- eth0
- eth1
gives (abridged)
msg: 'eth0: 10.1.0.10'
msg: 'eth1: 10.1.0.11'

Ansible - 'when' is not a valid attribute for a Play

I'm trying to figure out how to "remove" the warning message [WARNING]: Could not match supplied host pattern, ignoring: ps_nodes, by fixing the root cause. The root cause for me is that when we do Linux machine creation we will have the ps_nodes hosts empty. So, I was trying to add the block: + when: (os_type|capitalize) == "Windows", to assure that Play to only execute when os_type is a Windows creation.
How can I achieve that? Because, what I'm trying is to use the when condiction, but looks like it's not possible, and I'm not sure what to search anymore.
Code example:
- name: "Start handling of vm specific delete scripts for Windows machines"
block:
hosts: ps_nodes
any_errors_fatal: false
gather_facts: false
vars:
private_ip_1: "{{ hostvars['localhost']['_private_ip_1']|default('') }}"
scripts: "{{ hostvars['localhost']['scripts'] }}"
sh_script_dir: "{{ hostvars['localhost']['sh_script_dir'] }}"
cred_base_hst: "{{ hostvars['localhost']['cred_base_hst'] }}"
cred_base_gst: "{{ hostvars['localhost']['cred_base_gst'] }}"
newline: "\n"
tasks:
- import_tasks: roles/script/tasks/callWindowsScripts.yml
when: action == 'delete'
when: (os_type|capitalize) == "Windows"
Error using 'when' for a Play:
ERROR! 'when' is not a valid attribute for a Play
The error appears to be in '/opt/projectX/playbooks/create_vm.yml': line 265, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
##############################################################################
- name: \"Start handling of vm specific delete scripts for Windows machines\"
^ here
I think the problem is the indentation. Use 'and':
- name: "Start handling of vm specific delete scripts for Windows machines"
block:
hosts: ps_nodes
any_errors_fatal: false
gather_facts: false
vars:
private_ip_1: "{{ hostvars['localhost']['_private_ip_1']|default('') }}"
scripts: "{{ hostvars['localhost']['scripts'] }}"
sh_script_dir: "{{ hostvars['localhost']['sh_script_dir'] }}"
cred_base_hst: "{{ hostvars['localhost']['cred_base_hst'] }}"
cred_base_gst: "{{ hostvars['localhost']['cred_base_gst'] }}"
newline: "\n"
tasks:
- import_tasks: roles/script/tasks/callWindowsScripts.yml
when: action == 'delete' and (os_type|capitalize) == "Windows"
Got it,
What if you use a host that exists, like localhost, check the number of hosts in ps_nodes and delegate_to them?
Something like this:
hosts: localhost
vars:
tasks:
- import_tasks: roles/script/tasks/callWindowsScripts.yml
delegate_to: ps_nodes
when: {{ ps_nodes | length > 0}}
Same issue and fixed by "indent":
- hosts: test
roles:
- role: test
vars:
k: 1
when: "'dbg' in ansible_run_tags"

How to use condition while selecting hosts in Ansible

Below is my playlist. My doubt is, How do I use the IP which get from {{ hostvars['localhost']['srv'] }} and {{ hostvars['localhost']['srv1'] }
to the hosts with condition.
I wish to use like if telnet to server is successful then in next hosts, it should use {{ hostvars['localhost']['srv'] }} and if server1 telnet failed then hosts should get {{ hostvars['localhost']['srv1'] } and so on.
I tried to use OR in hosts but it is not working.
Is there any way to get this working?
- hosts: localhost
tasks:
- name: Telnet to server1
shell: 'telnet 10.2.0.150 8080'
register: pass
ignore_errors: yes
- set_fact:
ip={{pass.cmd| regex_findall('[0-9./]+') | list}}
register: ip_result
- set_fact:
srv={{ip_result.ansible_facts.ip[0]}}
register: srv
- debug: msg={{srv}}
- name: Telnet to server2
shell: "telnet 10.2.0.187 8080"
register: pass1
ignore_errors: yes
when: "'Connection refused' in pass.stderr or 'Connection timed out' in pass.stderr"
- set_fact:
ip1={{pass1.cmd| regex_findall('[0-9./]+') | list}}
register: ip1_result
when: "'Connection refused' in pass.stderr or 'Connection timed out' in pass.stderr"
- set_fact:
srv1={{ip1_result.ansible_facts.ip[0]}}
register: srv1
when: "'Connection refused' in pass.stderr or 'Connection timed out' in pass.stderr"
- debug: msg={{srv1}}
when: "'Connection refused' in pass.stderr or 'Connection timed out' in pass.stderr"
- hosts: "{{ hostvars['localhost']['srv'] }}" or "{{ hostvars['localhost']['srv1'] }}"
tasks:
I solve it by using jinja query.
- hosts: "{{ hostvars['localhost']['srv'] if 'Connection closed' in hostvars['localhost']['pass']['stderr'] else hostvars['localhost']['srv1']}}"
Tested it by stopping first server, then it took second servers and executed task to that server.
You can't change an inventory on-fly (you can't add or remove hosts from playbooks). But if all your hosts already are in an inventory, you can use group_by module to create groups inside plays.
https://docs.ansible.com/ansible/2.6/modules/group_by_module.html

Resources