Splitting dictionary not working in ansible.
Ansible- 2.5.15
Could anyone please help with any solution.
I was trying to fetch the values from dictionary but unable to fetch the values.
Tried code:
- hosts: localhost
connection: local
tasks:
- set_fact:
some_module: "{{ item.split(': ')[1] }}"
with_items:
- git: true
- gradle: false
Getting below error:
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'split'
Expected Results are like below:
[true, false]
You can handle it as a hashmap and get the key or the value:
- hosts: localhost
connection: local
tasks:
- set_fact:
some_module: "{{ item.values }}"
with_items:
- {git: true}
- {gradle: false}
(Updated for Ansible 2.9 and later)
Given the list
l:
- git: true
- gradle: false
the task
- set_fact:
out: "{{ l|map('dict2items')|
flatten|
map(attribute='value')|
list }}"
gives the required result
out:
- true
- false
If the data were a dictionary, e.g.
d:
git: true
gradle: false
the solution would have been much simpler, e.g. the task below gives the same result
- set_fact:
out: "{{ d.values()|list }}"
Notes
Your data is not a dictionary. It is a list
- git: true
- gradle: false
Dictionary is below
git: true
gradle: false
Let's create from the data a dictionary first and then use the dict2items filter.
The play below
- hosts: localhost
vars:
data1:
- {git: true}
- {gradle: false}
data2: {}
tasks:
- set_fact:
data2: "{{ data2|combine(item) }}"
loop: "{{ data1 }}"
- debug:
msg: "{{ data2|dict2items|json_query('[].value') }}"
gives:
"msg": [
true,
false
]
dict2items is available since Ansible 2.6. In older versions use simple filter_plugin hash_utils.py
$ cat filter_plugins/hash_utils.py
def hash_to_tuples(h):
return h.items()
def hash_keys(h):
return h.keys()
def hash_values(h):
return h.values()
class FilterModule(object):
''' utility filters for operating on hashes '''
def filters(self):
return {
'hash_to_tuples' : hash_to_tuples
,'hash_keys' : hash_keys
,'hash_values' : hash_values
}
The task below
- debug:
msg: "{{ data2|hash_values }}"
gives the same result as the construct with dict2items above. You might want to try other filters and review the details about filter_plugin.
Related
I have a json query that returns this result when run using set_fact and displaying using debug:
'''
Json_query: hits | community.general.json_query('results[?item==`xyz`].count')
debug output: TASK [debug] **********************************************************************************************************************************************
ok: [control] => {
"new_list8": "0"
}
Variable used in when conditional
matched: ["0"]
'''
In the JSON_query, "xyz" needs to be substituted by a loop variable in when conditional:
```
**- name: test
community.general.xml:
file: '/home/cloud_user/ansible/ansible_playbook/dev/xmlfiles/test.xml'
#backup: yes
pretty_print: true
xpath: /x:AgentMap
namespaces:
x: http://xyz
add_children:
- env:
name: "{{ server|upper }}"
_:
- agent:
_:
- name: "{{ server }}"
with_items:
- "{{ servers }}"
when: matched[0] in (hits | community.general.json_query('results[?item==`\" + server + \"`].count'))
loop_control:
loop_var: server**
```
Loop variable has been mentioned as seen in
<https://stackoverflow.com/questions/46038985/ansible-pass-a-variable-in-a-json-query-
filter>
When condition always evaluates to false.Task is to add a server to file if it doesn't already exist in it. Mentioned JSON_QUERY is to extract the count value from another task that checks number of occurrences. Query in JPTERM works fine too.`
fo your problem i rewrite your task like this:
```
**- name: test
community.general.xml:
file: '/home/cloud_user/ansible/ansible_playbook/dev/xmlfiles/test.xml'
#backup: yes
pretty_print: true
xpath: /x:AgentMap
namespaces:
x: http://xyz
add_children:
- env:
name: "{{ item|upper }}"
_:
- agent:
_:
- name: "{{ item }}"
when: matched[0] in (hits | community.general.json_query('results[?item==`\" ~ item ~ \"`].count'))
with_items:
- "{{ servers }}"
you cannot use an ansible variable inside the json_query.
specify the jmespath statement in a variable,
then use that in the json_query
- name: test
community.general.xml:
file: '/home/cloud_user/ansible/ansible_playbook/dev/xmlfiles/test.xml'
#backup: yes
pretty_print: true
xpath: /x:AgentMap
namespaces:
x: http://xyz
add_children:
- env:
name: "{{ server|upper }}"
_:
- agent:
_:
- name: "{{ server }}"
loop: "{{ servers }}"
when: matched[0] in (hits | community.general.json_query(my_query)
loop_control:
loop_var: server
vars:
my_query: 'results[?item==`{{ server }}`].count'
Using this yaml I'm trying to get a list of the 'Machine' attribute.
domainInfo:
AdminUserName: '--FIX ME--'
AdminPassword: '--FIX ME--'
topology:
Name: 'wld-pil-10'
ConfigBackupEnabled: true
AdminServerName: 'wls-pil-10-sa-adm-n0'
DomainVersion: 12.2.1.4.0
ProductionModeEnabled: true
ArchiveConfigurationCount: 20
Cluster:
'test-bruno-jee-r01a-c01':
ClientCertProxyEnabled: true
WeblogicPluginEnabled: true
Server:
'wls-pil-10-sa-adm-n0':
ListenPort: 11030
WeblogicPluginEnabled: true
ClientCertProxyEnabled: true
Machine: 'wlm-pil-10-n0'
'test-bruno-jee-r01a-it-c01-m1-n1':
ListenPort: 10022
WeblogicPluginEnabled: true
ClientCertProxyEnabled: true
NMSocketCreateTimeoutInMillis: 30000
Machine: 'wlm-pil-10-n1'
'test-bruno-jee-r02a-it-c01-m1-n1':
ListenPort: 10025
WeblogicPluginEnabled: true
ClientCertProxyEnabled: true
NMSocketCreateTimeoutInMillis: 30000
Machine: 'wlm-pil-10-n2'
I can get a list of the servers by putting the yaml in a variable named "yaml_domain_file" and this code:
set_fact:
servers: "{{ yaml_domain_file.topology.Server | list }}"
I get:
ok: [wls-pil-10-sa-adm-n0] => {
"msg": [
"wls-pil-10-sa-adm-n0",
"test-bruno-jee-r01a-it-c01-m1-n1",
"test-bruno-jee-r01a-it-c01-m1-n2"
]
}
I'm trying to get the list of machines with this code:
debug:
msg: "{{ yaml_domain_file.topology.Server.*.Machine | list }}"
but is not possible. How to get that information?
Thanks to everyone !
Try json_query
- debug:
msg: "{{ yaml_domain_file.topology.Server|json_query('*.Machine') }}"
Q: "what If I want to put each of the servers in an array?"
A: The simplest option is dict2items filter. For example
- set_fact:
servers: "{{ yaml_domain_file.topology.Server|dict2items }}"
In this task I found a roundabout method to compare two files (dconfDump and dconfDumpLocalCurrent) and to set a variable (previously defined as false) to true if the two files differ.
The solution seem to work, but it looks ugly and, as a beginner with ansible, I have the impression a better solution should be existing.
---
# vars file for dconfLoad
local_changed : false
target_changed : false
---
- name: local changed is true when previous target different then local current
shell: diff /home/frank/dconfDump /home/frank/dconfDumpLocalCurrent
register: diff_oldtarget_localCurrent
register: local_changed
ignore_errors: true
- debug:
msg: CHANGED LOCALLY
when: local_changed
Some background to the task, which is an attempt to synchronize files: A file LocalCurrent is compared with LocalOld and CurrentTarget, to determine if the LocalCurrent is changed and if it is different than currentTarget. If LocalCurrent is not changed and CurrentTarget is changed, then apply the change (and set LocalOld to CurrentTarget); if LocalCurrent is changed then upload to controller.
What is the appropriate approach with ansible? Thank you for help!
You can use stat to get the checksum and then compare it. Please see below.
tasks:
- name: Stat of dconfDump
stat:
path : "/tmp/dconfDump"
register: dump
- name: SHA1 of dconfDump
set_fact:
dump_sha1: "{{ dump.stat.checksum }}"
- name: Stat of dconfDumpLocalCurrent
stat:
path : "/tmp/dconfDumpLocalCurrent"
register: dump_local
- name: SHA1 of dconfDumpLocalCurrent
set_fact:
local_sha1: "{{ dump_local.stat.checksum }}"
- name: Same
set_fact:
val: "False"
when: dump_sha1 != local_sha1
- name: Different
set_fact:
val: "True"
when: dump_sha1 == local_sha1
- name: Print
debug:
msg: "{{val}}"
Use stat and create dictionary of checksums. For example
- stat:
path: "{{ item }}"
loop:
- LocalOld
- LocalCurrent
- CurrentTarget
register: result
- set_fact:
my_files: "{{ dict(paths|zip(chkms)) }}"
vars:
paths: "{{ result.results|map(attribute='stat.path')|list }}"
chkms: "{{ result.results|map(attribute='stat.checksum')|list }}"
- debug:
var: my_files
gives (abridged) if all files are the same
my_files:
CurrentTarget: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
LocalCurrent: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
LocalOld: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
Then use the dictionary to compare the checksums and copy files. For example
# If LocalCurrent is not changed and CurrentTarget is changed,
# then apply the change (and set LocalOld to CurrentTarget)
- debug:
msg: Set LocalOld to CurrentTarget
when:
- my_files['LocalCurrent'] == my_files['LocalOld']
- my_files['LocalCurrent'] != my_files['CurrentTarget']
- debug:
msg: Do not copy anything
when:
- my_files['LocalCurrent'] == my_files['LocalOld']
- my_files['LocalCurrent'] == my_files['CurrentTarget']
gives
TASK [debug] ****
skipping: [localhost]
TASK [debug] ****
ok: [localhost] =>
msg: Do not copy anything
I am trying to use set_fact, but only when an item is defined. I'm having trouble figuring out how to do this.
Playbook:
---
- hosts: localhost
vars:
apps:
- name: app1
role: "app1-role"
- name: app2
role: "app2-role"
- name: app4
role: "app4-role"
tasks:
- name: "Find a matching app, and print the values"
set_fact:
app: "{{ apps | selectattr('name', 'match', app_name) | first }}"
The task fails when there is no match, for example: app_name=app3, with this message:
FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: No first item, sequence was empty.
I have tried a few different conditionals, but I'm not quite sure how to structure this.
when: (apps | selectattr('name', 'match', app_name)) is undefined
This condition always evaluates as False - skipping: [localhost] => {"changed": false, "skip_reason": "Conditional result was False"}.
I tried this :
when: "{{ apps | selectattr('name', 'match', app_name) | list }}"
It seems that without the list filter, selectattr returning a generator object does not seem to allow converting to a boolean for the test evaluation. I'm not 100% sure about this interpretation though.
Note that having is defined or not would not change anything, it would be totally equivalent.
Try
- set_fact:
app: "{{ mylist|first }}"
vars:
mylist: "{{ apps|selectattr('name', 'match', app_name)|list }}"
when: mylist|length > 0
Next option is json_query instead of selectattr
- set_fact:
app: "{{ mylist|first }}"
vars:
query: "[?name=='{{ app_name }}']"
mylist: "{{ apps|json_query(query) }}"
when: mylist|length > 0
Looking for help with a problem I've been struggling with for a few hours. I want to iterate over a list, run a command, register the output for each command and then iterate with debug over each unique registers {{ someregister }}.stdout
For example, the following code will spit out "msg": "1" and "msg": "2"
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- name: "first"
int: "1"
- name: "second"
int: "2"
tasks:
- name: Register output
command: "/bin/echo {{ item.int }}"
register: result
with_items: "{{ numbers }}"
- debug: msg={{ item.stdout }}
with_items: "{{ result.results }}"
If however, I try and capture the output of a command in a register variable that is named using with_list, I am having trouble accessing the list or the elements within it. For example, altering the code slightly to:
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- name: "first"
int: "1"
- name: "second"
int: "2"
tasks:
- name: Register output
command: "/bin/echo {{ item.int }}"
register: "{{ item.name }}"
with_items: "{{ numbers }}"
- debug: var={{ item.name.stdout }}
with_items: "{{ numbers }}"
Gives me:
TASK [debug]
> ******************************************************************* fatal: [localhost]: FAILED! => {"failed": true, "msg": "'unicode
> object' has no attribute 'stdout'"}
Is it not possible to dynamically name the register the output of a command which can then be called later on in the play? I would like each iteration of the command and its subsequent register name to be accessed uniquely, e.g, given the last example I would expect there to be variables registered called "first" and "second" but there aren't.
Taking away the with_items from the debug stanza, and just explicitly defining the var or message using first.stdout returns "undefined".
Ansible version is 2.0.2.0 on Centos 7_2.
Thanks in advance.
OK so I found a post on stackoverflow that helped me better understand what is going on here and how to access the elements in result.results.
The resultant code I ended up with was:
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- name: "first"
int: "1"
- name: "second"
int: "2"
tasks:
- name: Register output
command: "/bin/echo {{ item.int }}"
register: echo_out
with_items: "{{ numbers }}"
- debug: msg="item.item={{item.item.name}}, item.stdout={{item.stdout}}"
with_items: "{{ echo_out.results }}"
Which gave me the desired result:
"msg": "item.item=first, item.stdout=1"
"msg": "item.item=second, item.stdout=2"
I am not sure if I understand the question correctly, but maybe this can help:
- debug: msg="{{ item.stdout }}"
with_items: echo_out.results
Please note that Ansible will print each item and the msg both - so you need to look carefully for a line that looks like "msg": "2".