Here is my database yaml file:
apn_operation:
action: create
epg_version: 4G
apn_name: internet_jack
access-restrictions selection-mode: public
routing-instance: sgi_nat
pgw-enabled: yes
uplink-dscp-map: test
allow-rule-space:
- RS_Mobile
- RS_Mobile_OCC
name-server:
- ip_address: 10.0.0.1
priority: 10
- ip_address: 10.0.0.2
priority: 20
Here is my playbook:
- hosts: all
gather_facts: no
vars_files:
- apn_operation.yaml
tasks:
- name: show content
debug:
msg: "{{item}}"
with_subelements:
- "{{apn_operation}}"
- name-server
when I ran the playbook. I got the following error:
TASK [show content] ******************************************************************************************************************************************************************************************
fatal: [11RRvEPG01]: FAILED! => {"msg": "subelements lookup expects a dictionary, got 'OPTUS-QCIDSCP'"}
I don't know what the problem is. can anyone help?
apn_operation is dictionary. It's not possible to iterate a dictionary. It's possible to use dict2items but this is not what you're looking for.
with_subelements is able to iterate a list where each item is a dictionary that comprises another list. For example
apn_operation:
- action: create
name-server:
- ip_address: 10.0.0.1
priority: 10
- ip_address: 10.0.0.2
priority: 20
works as expected and gives
"msg": [
{
"action": "create"
},
{
"ip_address": "10.0.0.1",
"priority": 10
}
]
"msg": [
{
"action": "create"
},
{
"ip_address": "10.0.0.2",
"priority": 20
}
]
Related
[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.
I'm working in Ansible 2.9.13 on Python 3.8.4
In an Ansible playbook, I have a list of dictionaries, and I want to loop on just the subset of items that have a pct_used value of greater than 90.
Here's a playbook that illustrates it:
---
- hosts: localhost
gather_facts: False
vars:
usage_records: [{mount: "/abc", pct_used: "50"}, {mount: "/def", pct_used: "75"}, {mount: "/ghi", pct_used: "95"}]
tasks:
- name: process just records with more than 90 percent usage
debug:
msg: "{{ item }}"
loop: "{{ usage_records|selectattr('pct_used','ge', 90)|list }}"
But if I run this, it complains about the fact that the pct_used values are AnsibleUnicode strings and not ints:
TASK [process just records with more than 90 percent usage] ************************************************************
fatal: [localhost]: FAILED! => {"msg": "Unexpected templating type error occurred on ({{ usage_records|selectattr('pct_used','ge', 90)|list }}): '>=' not supported between instances of 'AnsibleUnicode' and 'int'"}
Now, I realize the issue is that the pct_used value in the dictionary is a string and not an int. If I re-write the usage_records definition row without the quotes around the numbers:
usage_records: [{mount: "/abc", pct_used: 50}, {mount: "/def", pct_used: 75}, {mount: "/ghi", pct_used: 95}]
it does work as expected:
ok: [localhost] => (item={'mount': '/ghi', 'pct_used': 95}) => {
"msg": {
"mount": "/ghi",
"pct_used": 95
}
}
The problem is that in my actual use case, the data is being read in from a file and is all strings by default, so I can't change it at the source.
I feel like there must be a way to do the cast in the filter expression in the loop, but I just can't figure it out.
If that's NOT possible, what's the best way to update the list itself to cast the values to ints?
Well, here's one option:
---
- hosts: localhost
gather_facts: False
vars:
usage_records:
- mount: "/abc"
pct_used: "50"
- mount: "/def"
pct_used: "75"
- mount: "/ghi"
pct_used: "95"
tasks:
- name: process just records with more than 90 percent usage
debug:
msg: "{{ item }}"
loop: >-
{{ usage_records|zip(
usage_records|
map(attribute='pct_used')|
map('int')
)|
selectattr('1', 'ge', 90)|list
}}
This results in:
TASK [process just records with more than 90 percent usage] ***********************************
ok: [localhost] => (item=[{'mount': '/ghi', 'pct_used': '95'}, 95]) => {
"msg": [
{
"mount": "/ghi",
"pct_used": "95"
},
95
]
}
Here we're using to invocations of map to (a) create a second list consisting of just the pct_used values, and then (b) passing these all through the int filter. Finally, we zip together the original list with our new list. This gives us a final list where each item consists of the dictionary from the first list followed by the integer value from the second list (which we can use for filtering).
I think I found a solution that's cleaner for my purposes. Apparently you can use both a loop and a when clause, and the when clause can reference the loop variable. The when clause gives some more flexibility with the logic.
---
- hosts: localhost
gather_facts: False
vars:
usage_records: [{mount: "/abc", pct_used: 50}, {mount: "/def", pct_used: 75}, {mount: "/ghi", pct_used: 95}]
tasks:
- name: process just records with more than 90 percent usage
debug:
msg: "{{ item }}"
when: item.pct_used|int >= 90
loop: "{{ usage_records }}"
Still gets the desired output:
ok: [localhost] => (item={'mount': '/ghi', 'pct_used': 95}) => {
"msg": {
"mount": "/ghi",
"pct_used": 95
}
}
The first key of my host_var has a :. Like so,
---
openconfig-vlan:vlans:
vlan:
- vlan-id: '1001'
config:
vlan-id: 1001
name: test22
status: ACTIVE
However, I cannot seem to find a way to escape it so I can loop over the list within vlan.
Playbook
---
- name: Configure Devices via Native
hosts: ios
gather_facts: no
tasks:
- name: Create VLAN
ios_vlan:
vlan_id: "{{ item.config.vlan-id }}"
name: "{{ item.config.name }}"
state: present
with_items: "{{ openconfig-vlan:vlans['vlan'] }}"
Error
TASK [Create VLAN] ********************************************************************************************************************************************************************
fatal: [ios1]: FAILED! => {"msg": "template error while templating string: expected token 'end of print statement', got ':'. String: {{ openconfig-vlan:vlans['vlan'] }}"}
Any ideas? Thanks,
Q: "The first key of my host_var has a :. Like so,"
openconfig-vlan:vlans:
A: There are variables in in host_var no keys. Quoting from Creating valid variable names:
"Variable names should be letters, numbers, and underscores. Variables should always start with a letter."
There is only one idea available. Fix the syntax.
FWIW. For example, include the erroneous host_vars and put it into a valid variable. The play below
- hosts: localhost
tasks:
- include_vars:
file: vars-1-data.yml
name: test_var
- debug:
var: test_var['openconfig-vlan:vlans']
with the data
$ cat vars-1-data.yml
openconfig-vlan:vlans:
vlan:
- vlan-id: '1001'
config:
vlan-id: 1001
name: test22
status: ACTIVE
works as expected
"test_var['openconfig-vlan:vlans']": {
"vlan": [
{
"config": {
"name": "test22",
"status": "ACTIVE",
"vlan-id": 1001
},
"vlan-id": "1001"
}
]
}
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
I was given a task to verify some routing entries for all Linux server and here is how I did it using an Ansible playbook
---
- hosts: Linux
serial: 1
tasks:
- name: Check first
command: /sbin/ip route list xxx.xxx.xxx.xxx/24
register: result
changed_when: false
- debug: msg="{{result.stdout}}"
- name: Check second
command: /sbin/ip route list xxx.xxx.xxx.xxx/24
register: result
changed_when: false
- debug: msg="{{result.stdout}}"
You can see I have to repeat same task for each routing entry and I believe I should be able to avoid this. I tried use with_items loop but got following error message
One or more undefined variables: 'dict object' has no attribute 'stdout'
is there a way to register variable for each command and loop over them one by one ?
Starting in Ansible 1.6.1, the results registered with multiple items are stored in result.results as an array. So you can use result.results[0].stdout and so on.
Testing playbook:
---
- hosts: localhost
gather_facts: no
tasks:
- command: "echo {{item}}"
register: result
with_items: [1, 2]
- debug:
var: result
Result:
$ ansible-playbook -i localhost, test.yml
PLAY [localhost] **************************************************************
TASK: [command echo {{item}}] *************************************************
changed: [localhost] => (item=1)
changed: [localhost] => (item=2)
TASK: [debug ] ****************************************************************
ok: [localhost] => {
"var": {
"result": {
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": [
"echo",
"1"
],
"delta": "0:00:00.002502",
"end": "2015-08-07 16:44:08.901313",
"invocation": {
"module_args": "echo 1",
"module_name": "command"
},
"item": 1,
"rc": 0,
"start": "2015-08-07 16:44:08.898811",
"stderr": "",
"stdout": "1",
"stdout_lines": [
"1"
],
"warnings": []
},
{
"changed": true,
"cmd": [
"echo",
"2"
],
"delta": "0:00:00.002516",
"end": "2015-08-07 16:44:09.038458",
"invocation": {
"module_args": "echo 2",
"module_name": "command"
},
"item": 2,
"rc": 0,
"start": "2015-08-07 16:44:09.035942",
"stderr": "",
"stdout": "2",
"stdout_lines": [
"2"
],
"warnings": []
}
]
}
}
}
PLAY RECAP ********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
A slightly different situation, which took a while to figure out. If you want to use the results of multiple items, but for changed_when, then the register variable will not have a var.results! Instead, changed_when, is evaluated for each item, and you can just directly use the register var.
Simple example, which will result in changed: false:
- action: command echo {{item}}
register: out
changed_when: "'z' in out.stdout"
with_items:
- hello
- foo
- bye
Another example:
- name: Create fulltext index for faster text searches.
mysql_db: name={{SO_database}} state=import target=/tmp/fulltext-{{item.tableName}}-{{item.columnName}}.sql
with_items:
- {tableName: Posts, columnName: Title}
- {tableName: Posts, columnName: Body}
- {tableName: Posts, columnName: Tags}
- {tableName: Comments, columnName: Text}
register: createfulltextcmd
changed_when: createindexcmd.msg.find('already exists') == -1
Finally, when you do want to loop through results in other contexts, it does seem a bit tricky to programmatically access the index as that is not exposed. I did find this one example that might be promising:
- name: add hosts to known_hosts
shell: 'ssh-keyscan -H {{item.host}}>> /home/testuser/known_hosts'
with_items:
- { index: 0, host: testhost1.test.dom }
- { index: 1, host: testhost2.test.dom }
- { index: 2, host: 192.168.202.100 }
when: ssh_known_hosts.results[{{item.index}}].rc == 1
Posting because I can't comment yet
Relating to gameweld's answer, since Ansible 2.5 there's another way to accessing the iteration index.
From the docs:
Tracking progress through a loop with index_var
New in version 2.5.
To keep track of where you are in a loop, use the index_var directive
with loop_control. This directive specifies a variable name to contain
the current loop index:
- name: count our fruit
debug:
msg: "{{ item }} with index {{ my_idx }}"
loop:
- apple
- banana
- pear
loop_control:
index_var: my_idx
This also allows you to gather results from an array and act later to the same array, taking into account the previous results
- name: Ensure directories exist
file:
path: "{{ item }}"
state: directory
loop:
- "mouse"
- "lizard"
register: reg
- name: Do something only if directory is new
debug:
msg: "New dir created with name '{{ item }}'"
loop:
- "mouse"
- "lizard"
loop_control:
index_var: index
when: reg.results[index].changed
Please note that the "mouse lizard" array should be exactly the same
If what you need is to register the output of two commands separately, use different variable names.
---
- hosts: Linux
serial: 1
tasks:
- name: Check first
command: /sbin/ip route list xxx.xxx.xxx.xxx/24
register: result0
changed_when: false
- debug: msg="{{result0.stdout}}"
- name: Check second
command: /sbin/ip route list xxx.xxx.xxx.xxx/24
register: result1
changed_when: false
- debug: msg="{{result1.stdout}}"