Ansible json_query different result in python3 vs python2 - ansible

i bumped into an issue after migrated from python2 to python3. Seems that migration somehow changed the way how json query is being processed. Maybe anyone has a hint how to fix this
vars:
vmk_out:
host_vmk_info:
hostname:
[
{
ipv4_address: "10.10.10.101",
ipv4_subnet_mask: "255.255.255.0",
stack: "defaultTcpipStack"
},
{
ipv4_address: "10.10.20.101",
ipv4_subnet_mask: "255.255.255.0",
stack: "vmotion"
}
]
tasks:
- name: Extract list of IPs
set_fact:
output: "{{ vmk_out.host_vmk_info.values() |json_query('[].ipv4_address') }}"
Above ran under Python2 with Ansible 2.9.1 returns list of IP addresses but running the same under Python3 returns the empty list

I did not take time to dig into the root of the problem, but there is clearly a difference in the return of the values() function between python 2.7 and 3.x.
Here is what a direct debug or vmk_out.host_vmk_info.values() looks like from my tests:
ansible 2.9.1 - python 3.6
ok: [localhost] => {
"msg": "dict_values([[{'ipv4_address': '10.10.10.101', 'ipv4_subnet_mask': '255.255.255.0', 'stack': 'defaultTcpipStack'}, {'ipv4_address': '10.10.20.101', 'ipv4_subnet_mask': '255.255.255.0', 'stack': 'vmotion'}]])"
}
ansible 2.9.1 - python 2.7
ok: [localhost] => {
"msg": [
[
{
"ipv4_address": "10.10.10.101",
"ipv4_subnet_mask": "255.255.255.0",
"stack": "defaultTcpipStack"
},
{
"ipv4_address": "10.10.20.101",
"ipv4_subnet_mask": "255.255.255.0",
"stack": "vmotion"
}
]
]
}
You have 2 solutions to fix your current code and make it compatible with both versions.
Solution 1: make sure the output of values() always produces a list:
output: "{{ vmk_out.host_vmk_info.values() | list | json_query('[].ipv4_address') }}"
Solution 2: stop using values() and directly map the existing hostname list
output: "{{ vmk_out.host_vmk_info.hostname | json_query('[].ipv4_address') }}"

Related

selectattr returns generator rows and cannot use results as a dict

SOLUTION:
I Don't know what is the exact difference between python -m pip install ansible or apt install ansible but when I installed python -m pip ansible-core==2.10.4 it works fine.
I have CSV file which looks like:
id;env;credentials;path
1;tst;userA;/tmpA
2;dev;userB;/tmpB
3;dev;userB;/tmpC
4;acc;userB;/tmpD
5;prd;userC;/tmpE
I read this file using read_csv module and then I'm filtering using selectattr:
- name: Read CSV
read_csv:
path: "/tmp/example.csv"
delimiter: ';'
register: csv_output
- name: Filter rows
set_fact:
new_fact: "{{ csv_output.list | selectattr('env', 'equalto', tst) }}"
In the past I was able to just use these results as a dict so for example:
- debug:
msg: "{{ new_fact }}"
ok: [ansible_main] => {
"msg": [
{
"id": "1",
"env": "tst",
"credentials": "userA",
"path": "/tmpA"
}
]
}
but when I try to print new_fact on my local machine I see only generator:
ok: [ansible_main] => {
"msg": "<generator object select_or_reject at 0x7f2e4e8847b0>"
}
and I cannot use new_fact.credentials variable... Do you know how can I fix it? I know I can add | list at the end of my filter but then I also cannot use new_fact.credentials
Details of my installation:
ansible 2.9.6
config file = /etc/ansible/ansible.cfg
configured module search path = ['/home/userA/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.8.10 (default, Nov 26 2021, 20:14:08) [GCC 9.3.0]
Regarding
In the past I was able to just use these results as a dict ...
was it a much newer version of Ansible than v2.9.6?
I know I can add | list at the end of my filter but then I also cannot user new_fact.credentials then
Since you will get a list too, it will be necessary to specify the element
- name: Filter rows
set_fact:
new_fact: "{{ csv_output.list | selectattr('env', 'contains', 'tst') | list }}"
- debug:
msg: "{{ new_fact[0].credentials }}"
to loop over the result, or pickup one element only.
- name: Filter rows
set_fact:
new_fact: "{{ csv_output.list | selectattr('env', 'contains', 'tst') | first }}"
- debug:
msg: "{{ new_fact.credentials }}"

Difficulty Parsing Ansible Output

[ansible 2.9.6, Ubuntu 20.04]
This seems pretty straightforward, but I keep getting an error message saying:
fatal: [192.168.254.100]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ipv4addrs.'host[0]'}}"}
Here's my ansible playbook:
---
- hosts: nios
connection: local
vars:
nios_provider:
host: 192.x.x.x
username: xxx
password: xxx
wapi_version: "2.11.2"
tasks:
- name: Find client app server records
set_fact:
recs: "{{ lookup('nios', 'record:host', filter={'name~':'sdk' }, provider=nios_provider) }}"
- name: check return
debug:
msg: "{{ recs }}"
- name: get host name
debug:
var: ipv4addrs.'host[0]'
And here's the output:
TASK [check return] **************************************************************************************************************************************************************
ok: [192.x.x.x] => {
"msg": [
{
"_ref": "record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5lYWdsZWFjY2Vzcy5zZGstZDAwMQ:sdk-d001.somename.com/default",
"ipv4addrs": [
{
"_ref": "record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuY29tLmVhZ2xlYWNjZXNzLnNkay1kMDAxLjEwLjcwLjAuMS4:10.70.0.1/sdk-d001.somename.com/default",
"configure_for_dhcp": false,
"host": "sdk-d001.somename.com",
"ipv4addr": "10.70.0.1"
}
],
"name": "sdk-d001.somename.com",
"view": "default"
},
{
"_ref": "record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5lYWdsZWFjY2Vzcy5zZGstZDAwMg:sdk-d002.somename.com/default",
"ipv4addrs": [
{
"_ref": "record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuY29tLmVhZ2xlYWNjZXNzLnNkay1kMDAyLjEwLjcwLjAuMi4:10.70.0.2/sdk-d002.somename.com/default",
"configure_for_dhcp": false,
"host": "sdk-d002.somename.com",
"ipv4addr": "10.70.0.2"
}
],
"name": "sdk-d002.somename.com",
"view": "default"
}
]
}
TASK [get host name] ***************************************************************************************************************************************************************
fatal: [192.168.254.100]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ipv4addrs.'host[0]'}}"}
My objective is to find all of the host names beginning with (for example) "sdk". There may be 1 or several. And then I want to get the full name and ip address captured as variables. I have tried lots of different options: rec.ipv4addrs[0].host, ipv4addrs[0].host, rec.ipv4addrs[0].'host', rec.ipv4addrs[0].['host'] . . . but I cannot find the proper syntax.
This appears to be an array of dict blocks with an ipv4addrs array within it. So the first thing I tried was "ipv4addrs[0]['host']" with no joy.
Thanks in advance for the help.
You're trying to make use of a variable called ipv4addrs, but you
haven't defined any variables with that name. From the previous
debug task you apparently have a variable named recs, which is a
list of dictionaries each of which contains an ipv4addrs attribute.
If you wanted to look up the host attribute for each record in
recs, you could write something like this:
- name: get host name
debug:
var: item.ipv4addrs[0].host
loop: "{{ recs }}"
If you wanted the ipv4addrs.host value from the first record, you
could write:
- name: get host name
debug:
var: recs[0].ipv4addrs[0].host
The error you're seeing stems from the fact that the expression
ipv4addrs.'host[0]' doesn't make any sense; that's not a syntax that
Ansible supports. You can't use a string with dot-notation like that.

Ansible how to get value from dictionary using a mix of variable and string as key

Im trying to achieve something like that , being easier saying than implementing, basically I want to get value from dictionary using a mix of variable and string as key at an import_task, so the code should be something like that :
- import_tasks: "somefile_{{ somedic[ansible_distribution'_'ansible_distribution_major_version ] }}.yml"
The dic should be something like that :
somedic: { "RedHat_7": "endoffilename" }
And when run on a server RedHat 7 it should end loading a file called
somefile_endoffilename.yml
You cannot do it with that dictionary structure, because it requires nested variable dereferencing, which Jinja2 does not support. So your dictionary needs to look like this:
vars:
somedic:
"RedHat":
"7": "endoffilename"
In JSON, that looks like:
vars:
somedic: { "RedHat": { "7": "endoffilename" } }
Then, you can get what you need:
---
- hosts: localhost
gather_facts: no
vars:
somedic: { "RedHat": { "7": "endoffilename" } }
OS: RedHat
MajorVersion: "7"
tasks:
- debug:
msg: "somefile_{{ somedic[OS][MajorVersion] }}.yml"
The output of the debug task is:
TASK [debug] *************************************************************************
Tuesday 26 May 2020 11:14:30 -0400 (0:00:00.027) 0:00:00.146 ***********
ok: [localhost] => {
"msg": "somefile_endoffilename.yml"
}

Ansible: How to specify an array or list element fact with yaml?

When we check hostvars with:
- name: Display all variables/facts known for a host
debug: var=hostvars[inventory_hostname]
We get:
ok: [default] => {
"hostvars[inventory_hostname]": {
"admin_email": "admin#surfer190.com",
"admin_user": "root",
"ansible_all_ipv4_addresses": [
"192.168.35.19",
"10.0.2.15"
],...
How would I specify the first element of the "ansible_all_ipv4_addresses" list?
Use dot notation
"{{ ansible_all_ipv4_addresses.0 }}"
This should work just like it would in Python. Meaning you can access the keys with quotes and the index with an integer.
- set_fact:
ip_address_1: "{{ hostvars[inventory_hostname]['ansible_all_ipv4_addresses'][0] }}"
ip_address_2: "{{ hostvars[inventory_hostname]['ansible_all_ipv4_addresses'][1] }}"
- name: Display 1st ipaddress
debug:
var: ip_address_1
- name: Display 2nd ipaddress
debug:
var: ip_address_2
I had this same challenge when trying to parse the result of a command in Ansible.
So the result was:
{
"changed": true,
"instance_ids": [
"i-0a243240353e84829"
],
"instances": [
{
"id": "i-0a243240353e84829",
"state": "running",
"hypervisor": "xen",
"tags": {
"Backup": "FES",
"Department": "Research"
},
"tenancy": "default"
}
],
"tagged_instances": [],
"_ansible_no_log": false
}
And I wanted to parse the value of state into the result register in the ansible playbook.
Here's how I did it:
Since the result is an hash of array of hashes, that is state is in the index (0) hash of the instances array, I modified my playbook to look this way:
---
- name: Manage AWS EC2 instance
hosts: localhost
connection: local
# gather_facts: false
tasks:
- name: AWS EC2 Instance Restart
ec2:
instance_ids: '{{ instance_id }}'
region: '{{ aws_region }}'
state: restarted
wait: True
register: result
- name: Show result of task
debug:
var: result.instances.0.state
I saved the value of the command using register in a variable called result and then got the value of state in the variable using:
result.instances.0.state
This time when the command ran, I got the result as:
TASK [Show result of task] *****************************************************
ok: [localhost] => {
"result.instances.0.state": "running"
}
That's all.
I hope this helps

Ansible date variable

I'm trying to learn how to use Ansible facts as variables, and I don't get it. When I run...
$ ansible localhost -m setup
...it lists all of the facts of my system. I selected one at random to try and use it, ansible_facts.ansible_date_time.date, but I can't figure out HOW to use it. When I run...
$ ansible localhost -m setup -a "filter=ansible_date_time"
localhost | success >> {
"ansible_facts": {
"ansible_date_time": {
"date": "2015-07-09",
"day": "09",
"epoch": "1436460014",
"hour": "10",
"iso8601": "2015-07-09T16:40:14Z",
"iso8601_micro": "2015-07-09T16:40:14.795637Z",
"minute": "40",
"month": "07",
"second": "14",
"time": "10:40:14",
"tz": "MDT",
"tz_offset": "-0600",
"weekday": "Thursday",
"year": "2015"
}
},
"changed": false
}
So, it's CLEARLY there. But when I run...
$ ansible localhost -a "echo {{ ansible_facts.ansible_date_time.date }}"
localhost | FAILED => One or more undefined variables: 'ansible_facts' is undefined
$ ansible localhost -a "echo {{ ansible_date_time.date }}"
localhost | FAILED => One or more undefined variables: 'ansible_date_time' is undefined
$ ansible localhost -a "echo {{ date }}"
localhost | FAILED => One or more undefined variables: 'date' is undefined
What am I not getting here? How do I use Facts as variables?
The command ansible localhost -m setup basically says "run the setup module against localhost", and the setup module gathers the facts that you see in the output.
When you run the echo command these facts don't exist since the setup module wasn't run. A better method to testing things like this would be to use ansible-playbook to run a playbook that looks something like this:
- hosts: localhost
tasks:
- debug: var=ansible_date_time
- debug: msg="the current date is {{ ansible_date_time.date }}"
Because this runs as a playbook facts for localhost are gathered before the tasks are run. The output of the above playbook will be something like this:
PLAY [localhost] **************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [debug var=ansible_date_time] *******************************************
ok: [localhost] => {
"ansible_date_time": {
"date": "2015-07-09",
"day": "09",
"epoch": "1436461166",
"hour": "16",
"iso8601": "2015-07-09T16:59:26Z",
"iso8601_micro": "2015-07-09T16:59:26.896629Z",
"minute": "59",
"month": "07",
"second": "26",
"time": "16:59:26",
"tz": "UTC",
"tz_offset": "+0000",
"weekday": "Thursday",
"year": "2015"
}
}
TASK: [debug msg="the current date is {{ ansible_date_time.date }}"] **********
ok: [localhost] => {
"msg": "the current date is 2015-07-09"
}
PLAY RECAP ********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
The lookup module of ansible works fine for me. The yml is:
- hosts: test
vars:
time: "{{ lookup('pipe', 'date -d \"1 day ago\" +\"%Y%m%d\"') }}"
You can replace any command with date to get result of the command.
Note that the ansible command doesn't collect facts, but the ansible-playbook command does. When running ansible -m setup, the setup module happens to run the fact collection so you get the facts, but running ansible -m command does not. Therefore the facts aren't available. This is why the other answers include playbook YAML files and indicate the lookup works.
The filter option filters only the first level subkey below ansible_facts
I tried the lookup('pipe,'date') method and got trouble when I push the playbook to the tower. The tower is somehow using UTC timezone. All play executed as early as the + hours of my TZ will give me one day later of the actual date.
For example: if my TZ is Asia/Manila I supposed to have UTC+8. If I execute the playbook earlier than 8:00am in Ansible Tower, the date will follow to what was in UTC+0. It took me a while until I found this case. It let me use the date option '-d \"+8 hours\" +%F'. Now it gives me the exact date that I wanted.
Below is the variable I set in my playbook:
vars:
cur_target_wd: "{{ lookup('pipe','date -d \"+8 hours\" +%Y/%m-%b/%d-%a') }}"
That will give me the value of "cur_target_wd = 2020/05-May/28-Thu" even I run it earlier than 8:00am now.

Resources