How to create a nested directories structure via Ansible - ansible

Finally, I found a solution on how to create nested directory structure which is an equivalent of the Bash command
mkdir -p jenkins/cache/{war,tmp,workspace}
My playbook
---
- name: Create directories
hosts: localhost
vars:
dirs: [
jenkins, # for one elemnt it can be string
[ cache ],
[ war, tmp, workspace ]
]
base_directory: ~/tmp
tasks:
- debug:
msg="{{base_directory}}/{{item.0}}/{{item.1}}/{{item.2}}"
with_nested: "{{ dirs | list }}"
It generates:
PLAY [Create directories] **********************************************************************************************************************************
TASK [debug] ***********************************************************************************************************************************************
ok: [localhost] => (item=['jenkins', 'cache', 'war']) => {
"msg": "~/tmp/jenkins/cache/war"
}
ok: [localhost] => (item=['jenkins', 'cache', 'tmp']) => {
"msg": "~/tmp/jenkins/cache/tmp"
}
ok: [localhost] => (item=['jenkins', 'cache', 'workspace']) => {
"msg": "~/tmp/jenkins/cache/workspace"
}
PLAY RECAP *************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
But there is an issue if I want to use more (or less) levels of nesting. I need more (or less) item.X.
How to do it dynamically in Ansible?
I know there is community module filetree, which can be helpfully but I don't want or need to use it.

You can definitely use a with_nested and join the second level list with a /.
So, a task like this would do:
- debug:
msg: >-
{{ base_directory }}/
{{- item | join('/') }}
with_nested: "{{ dirs }}"
Given the task:
- debug:
msg: >-
{{ base_directory }}/
{{- item | join('/') }}
with_nested: "{{ dirs }}"
vars:
dirs:
- jenkins
- - cache
- - war
- tmp
- workspace
base_directory: ~/tmp
This yields:
ok: [localhost] => (item=['jenkins', 'cache', 'war']) =>
msg: ~/tmp/jenkins/cache/war
ok: [localhost] => (item=['jenkins', 'cache', 'tmp']) =>
msg: ~/tmp/jenkins/cache/tmp
ok: [localhost] => (item=['jenkins', 'cache', 'workspace']) =>
msg: ~/tmp/jenkins/cache/workspace

The filter community.general.path_join takes the items of a list and creates a path. For example, the simplified task below
- debug:
msg: "{{ base_directory }}/{{ item|community.general.path_join }}"
with_nested: "{{ dirs }}"
vars:
dirs:
- jenkins
- cache
- [war, tmp, workspace]
base_directory: /tmp
gives
msg: /tmp/jenkins/cache/war
msg: /tmp/jenkins/cache/tmp
msg: /tmp/jenkins/cache/workspace

Related

Iterating list of dictionaries with dictionary

I'm having trouble grasping data manipulation in Ansible. Here's the setup:
- hosts: emirr01
gather_facts: no
vars:
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
use_labels: [ ]
tasks:
- debug: msg="setup={{ setup }}"
- debug: msg="lookup={{ lookup }}"
- debug: msg="item0={{ item.0 }} item1={{ item.1 }}"
when: inventory_hostname == item.1
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- set_fact:
use_labels: "{{ use_labels + [ item.1.label ] }}"
when: item.0 == item.1.label
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- debug: msg="use_labels={{ use_labels }}"
What I need is to set a fact use_labels which is a list of labels as defined in setup list for each host found in lookup list. What I expect is a list like this:
[ "label1", "label2" ]
My problem is not being able to "reach" label in a list I get in item.1 which is (example):
item1=[{'emirr02': {'label': 'label2'}}
Here's is the error:
$ ansible-playbook test.yml -l emirr01
PLAY [emirr01] ****************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: 'setup=[{''emirr01'': {''label'': ''label1''}}, {''emirr02'': {''label'': ''label2''}}, {''emirr03'': {''label'': ''label3''}}]'
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: lookup=['emirr01', 'emirr02']
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01']) =>
msg: 'item0={''emirr01'': {''label'': ''label1''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01']) =>
msg: 'item0={''emirr02'': {''label'': ''label2''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01']) =>
msg: 'item0={''emirr03'': {''label'': ''label3''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [set_fact] ***************************************************************************************************************************************************************************************************************************************************************************************************************************
fatal: [emirr01]: FAILED! =>
msg: |-
The conditional check 'item.0 == item.1.label' failed. The error was: error while evaluating conditional (item.0 == item.1.label): 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'label'
The error appears to be in '/home/ansible/test.yml': line 21, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- set_fact:
^ here
It all boils down to "digging" for a value from a dictionary in a list. Does anyone have an idea of how to reach item.1.label? It's a little bit frustrating since this is trivial in Python or other programming languages but in Ansible... Or maybe my mistake is to use a wrong kind of with_* loop for this purpose?
Q: "Expect a list like this [label1, label2]"
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
A: This is a typical example of a wrong data structure for this use-case. A list of dictionaries can't be used in mapping the filter extract. Moreover, the keys in the dictionaries don't have to be unique, because they are hidden in the items of the list. The solution is simple with the data in the dictionary
setup:
emirr01: { label: "label1" }
emirr02: { label: "label2" }
emirr03: { label: "label3" }
- set_fact:
use_labels: "{{ lookup|map('extract', setup, 'label')|list }}"
gives
use_labels:
- label1
- label2
One of the options is creating a dictionary from the list first. For example
- set_fact:
setup2: "{{ setup2|default({})|combine(item) }}"
loop: "{{ setup }}"
Then the dictionary setup2 can be used to get the same result
It's possible to filter the list directly. For example, the task below gives the same result too
- set_fact:
use_labels: "{{ setup|map('dict2items')|map('first')|
selectattr('key', 'in', lookup )|
map(attribute='value.label')|
list }}"
Your issue with the with_nested is actually coming from how those "two-lists" loops are working.
When you are using loops like with_nested, the item.0 is the item of the first list, while the item.1 is the item of the second list.
Because lookup is a list and not a list of dictionaries like
lookup:
- label: "emirr01"
- label: "emirr02"
You can use the value of item.1 right away.
So the way to have you issue fixed is to use it this way:
- set_fact:
use_labels: "{{ use_labels + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
Here is a playbook demonstrating this:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
use_labels: "{{ use_labels | default([]) + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
vars:
setup:
- emirr01:
label: "label1"
- emirr02:
label: "label2"
- emirr03:
label: "label3"
lookup:
- "emirr01"
- "emirr02"
- debug:
msg: "{{ use_labels }}"
Wich gives:
PLAY [localhost] ***************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01'])
ok: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [debug] *******************************************************************************************************
ok: [localhost] => {
"msg": [
"label1",
"label2"
]
}
PLAY RECAP *********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Split debug variable output into two separate variables in ansible

I am using below code snippet where image details would be printed:
- set_fact:
image_name: "{{ load.results|map(attribute='stdout_lines')|list }}"
- debug:
var: image_name
Output:
TASK [set_fact] ***************************************************************************************************************************************************************************
ok: [xx.xx.xx.xx]
TASK [debug] ******************************************************************************************************************************************************************************
ok: [xx.xx.xx.xx] => {
"image_name": [
[
"Loaded image(s): localhost/cim:v1.5"
],
[
"Loaded image(s): localhost/cim:v1.8"
]
]
}
Is there a way I can store the image name and tag in two separate variables under set_fact itself or in any other form so I can reuse those 2 variables for the next task?
You can use a regex_findall filter in order to achieve this.
The regex used here is (\S*):(\S+). if needed, more explanation on it can be found here
Given the playbook:
- hosts: all
gather_facts: no
vars:
load:
results:
- stdout_lines:
- "Loaded image(s): localhost/cim:v1.5"
- stdout_lines:
- "Loaded image(s): localhost/cim:v1.8"
tasks:
- set_fact:
images: "{{ images | default([]) + item | regex_findall('(\\S*):(\\S+)') }}"
loop: "{{ load.results | map(attribute='stdout_lines') | flatten }}"
- debug:
msg: "This image repository is `{{ item.0 }}` and its tag is `{{ item.1 }}`"
loop: "{{ images }}"
This yields the recap:
PLAY [all] *********************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [localhost] => (item=Loaded image(s): localhost/cim:v1.5)
ok: [localhost] => (item=Loaded image(s): localhost/cim:v1.8)
TASK [debug] *******************************************************************************************************
ok: [localhost] => (item=['localhost/cim', 'v1.5']) => {
"msg": "This image repository is `localhost/cim` and its tag is `v1.5`"
}
ok: [localhost] => (item=['localhost/cim', 'v1.8']) => {
"msg": "This image repository is `localhost/cim` and its tag is `v1.8`"
}
PLAY RECAP *********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Iterating via nested loops

The packages.yml file defined as:
---
- packages:
- name: Some description 1,
packageList:
- package1,
- package2,
- package3
- name: Some description 2,
package: package4
The first item contains a field packageList, the 2nd item does not have it, but only package field.
Playbook:
---
- hosts: all
become: yes
vars_files:
- packages.yml
How to iterate via all packageList items of the packages list only if this packageList is defined for an item.
Here is how I can iterate through items which contain package field:
- name: iteration
debug:
msg: "name: {{ item.package }}"
when: item.package is defined
with_items: "{{ packages }}"
As noted in my comment, if you simply want to install multiple yum/apt packages, it is usually more efficient to simply pass the list to the apt/yum/package module. As the docs state:
"When used with a loop: each package will be processed individually, it is much more efficient to pass the list directly to the name option."
However, if you really need a loop, here is a possible solution:
playbook.yml:
---
- hosts: all
gather_facts: false
vars_files:
- packages.yml
tasks:
- name: iteration over single items
debug:
msg: "name: {{ item.package }}"
when: item.package is defined
with_items: "{{ packages }}"
- name: iteration over lists
debug:
msg: "name: {{ item.packageList }}"
when: item.packageList is defined
with_items: "{{ packages }}"
- name: Do something with individual packages in the list
include_tasks: process_list.yml
vars:
mylist: "{{outer.packageList}}"
when: outer.packageList is defined
loop: "{{ packages }}"
loop_control:
loop_var: outer
process_list.yml:
- name: See what we have received
debug:
var: item
loop: "{{mylist}}"
result:
PLAY [all] *******************************************************************************************************************************
TASK [iteration over single items] *******************************************************************************************************
skipping: [localhost] => (item={u'packageList': [u'package1,', u'package2,', u'package3'], u'name': u'Some description 1,'})
ok: [localhost] => (item={u'name': u'Some description 2,', u'package': u'package4'}) => {
"msg": "name: package4"
}
TASK [iteration over lists] **************************************************************************************************************
ok: [localhost] => (item={u'packageList': [u'package1,', u'package2,', u'package3'], u'name': u'Some description 1,'}) => {
"msg": "name: [u'package1,', u'package2,', u'package3']"
}
skipping: [localhost] => (item={u'name': u'Some description 2,', u'package': u'package4'})
TASK [Do something with individual packages in the list] *********************************************************************************
skipping: [localhost] => (item={u'name': u'Some description 2,', u'package': u'package4'})
included: /root/tmp/process_list.yml for localhost
TASK [See what we have received] *********************************************************************************************************
ok: [localhost] => (item=package1,) => {
"ansible_loop_var": "item",
"item": "package1,"
}
ok: [localhost] => (item=package2,) => {
"ansible_loop_var": "item",
"item": "package2,"
}
ok: [localhost] => (item=package3) => {
"ansible_loop_var": "item",
"item": "package3"
}
PLAY RECAP *******************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The loop_control/loop_var part is used because otherwise both the outer and inner loop will use {{item}} as the loop variable - and this will cause... interesting results :)
You can define a default value with an empty list for the cases, where the packageList is undefined.
{{ item.packageList | default ([]) }}
If the packageList is undefined, the job iterates over an empty list, which means, it does not do anything.
You can use default, as #ceving mentioned:
---
- hosts: localhost
connection: local
vars_files:
- packages.yml
tasks:
- name: iteration
debug:
msg: "name: {{ item.packageList | default([item.package]) }}"
with_items: "{{ packages }}"
If packageList exists, it will use that, else package put into a single-element array to match the form of packageList:
PLAY [localhost] **********************************************************************************************
TASK [Gathering Facts] ****************************************************************************************
ok: [localhost]
TASK [iteration] **********************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "name: [u'package1,', u'package2,', u'package3']"
}
ok: [localhost] => (item=None) => {
"msg": "name: [u'package4']"
}
PLAY RECAP ****************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

Get all first level folders of archive module run

I'm using the archive module to zip some folders and archives.
I need to list all first level folders inside my zip file.
Is there any Jinja or python function to do this using the registered output of the archive module?
My test, so far are:
- name: zip folder
archive:
path:
- /tmp/test/*
dest: /tmp/zipfile.zip
format: zip
mode: "0755"
force_archive: true
register: module_result
Printing module_result, I get:
ok: [host1] =>
msg:
ansible_facts:
discovered_interpreter_python: /usr/bin/python
archived:
- /tmp/test/cfg/1.txt
- /tmp/test/cfg/2.txt
- /tmp/test/cfg/3.txt
- /tmp/test/folder1/folder2/4.txt
- /tmp/test/folder1/folder2/5.txt
Using another task to show my attempts:
- name: list folders
debug:
msg: "{{ item.lstrip(module_result.arcroot).split('/')[0] }}"
# msg: "{{ item }}"
with_items: "{{module_result.archived}}"
I'm trying to cut the first part of the url, using module_result.arcroot on every item that module outputs me and then, a split of the rest of the path, but I get a wrong result. And the worst part is that I'm getting too many times the same folder name.
And the result:
cfg
cfg
cfg
folder1
folder1
My expected result would be:
cfg
folder1
Is this possible?
If your goal is to achieve an unicity on the folders of first level, then the unique Jinja filter is what you are looking for.
Since this filter acts on lists, you would then need to, first, register your results as a list, via the module set_fact.
- name: Making a list out of the files first folder
set_fact:
folders: "{{ folders | default([]) + [(item.lstrip(faked_module_result_arcroot).split('/')[0])] }}"
with_items: "{{ faked_module_result_archived }}"
Then just use the Jinja filter:
- debug:
msg: "{{ folders | unique }}"
Fully working playbook:
---
- hosts: localhost
connection: local
vars:
faked_module_result_archived:
- /tmp/test/cfg/1.txt
- /tmp/test/cfg/2.txt
- /tmp/test/cfg/3.txt
- /tmp/test/folder1/folder2/4.txt
- /tmp/test/folder1/folder2/5.txt
faked_module_result_arcroot: /tmp/test/
tasks:
- name: Making an list out of the files first folder
set_fact:
folders: "{{ folders | default([]) + [(item.lstrip(faked_module_result_arcroot).split('/')[0])] }}"
with_items: "{{ faked_module_result_archived }}"
- debug:
msg: "{{ folders | unique }}"
Would output
PLAY [localhost] ************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************
ok: [localhost]
TASK [Making an list out of the files first folder] ************************************************************************************************************************
ok: [localhost] => (item=/tmp/test/cfg/1.txt)
ok: [localhost] => (item=/tmp/test/cfg/2.txt)
ok: [localhost] => (item=/tmp/test/cfg/3.txt)
ok: [localhost] => (item=/tmp/test/folder1/folder2/4.txt)
ok: [localhost] => (item=/tmp/test/folder1/folder2/5.txt)
TASK [debug] ****************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"cfg",
"folder1"
]
}
PLAY RECAP ******************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Your example works great! I could get some tips and tricks to customize mi script, but for some reason, I'm am getting a strange lstrip behavior when I run the adaptation.
Here is my code:
- name: zip a folder
archive:
path:
- /tmp/test/*
dest: /tmp/{{ path_example | basename }}-files.zip
exclude_path:
- /tmp/test/another_folder
format: zip
mode: "0755"
force_archive: true
register: module_result
- name: First, lstrip result
set_fact:
lstrip_result: "{{ lstrip_result | default([]) [(item.lstrip(module_result.arcroot))] }}"
with_items: "{{ module_result.archived }}"
After run "Archive" task:
ok: [desa01] => changed=false
ansible_facts:
discovered_interpreter_python: /usr/bin/python
archived:
- /tmp/test/cfg/1.txt
- /tmp/test/cfg/2.txt
- /tmp/test/cfg/3.txt
- /tmp/test/folder1/folder2/4.txt
- /tmp/test/folder1/folder2/5.txt
arcroot: /tmp/test/
dest: /tmp/lelo/test-files.zip
expanded_exclude_paths:
- /tmp/test/another_folder
After run "First, lstrip result" task:
TASK [task2: First remove arcroot using lstrip ] ********************************************************************************************************
ok: [desa01] => (item=/tmp/test/cfg/1.txt) => changed=false
ansible_facts:
lstrip_result:
- cfg/1.txt
ansible_loop_var: item
item: /tmp/test/cfg/1.txt
ok: [desa01] => (item=/tmp/test/cfg/2.txt) => changed=false
ansible_facts:
lstrip_result:
- cfg/1.txt
- cfg/2.txt
ansible_loop_var: item
item: /tmp/test/cfg/2.txt
ok: [desa01] => (item=/tmp/test/cfg/3.txt) => changed=false
ansible_facts:
lstrip_result:
- cfg/1.txt
- cfg/2.txt
- cfg/3.txt
ansible_loop_var: item
item: /tmp/test/cfg/3.txt
ok: [desa01] => (item=/tmp/test/folder1/folder2/4.txt) => changed=false
ansible_facts:
lstrip_result:
- cfg/1.txt
- cfg/2.txt
- cfg/3.txt
- st/folder1/folder2/4.txt
ansible_loop_var: item
item: /tmp/test/folder1/folder2/4.txt
ok: [desa01] => (item=/tmp/test/folder1/folder2/5.txt) => changed=false
ansible_facts:
lstrip_result:
- cfg/1.txt
- cfg/2.txt
- cfg/3.txt
- st/folder1/folder2/4.txt
- st/folder1/folder2/4.txt
ansible_loop_var: item
item: /tmp/test/folder1/folder2/5.txt
Here is my problem, with folder cfg everything it is ok, but with folder test, lstrip function is like striping 2 chars more than expected and the result is:
st/folder...
instead of:
test/folder...
Someone gets an idea of this strange lstrip out??
EDIT
lstrip doc says:
Definition and Usage
The lstrip() method removes any leading characters (space is the default leading character to remove)
That's why my output was bad striped, becouse some chars where repited in the string.
I replace lstrip with replace
- name: First, lstrip result
set_fact:
lstrip_result: "{{ lstrip_result | default([]) [(item.lstrip(module_result.arcroot))] }}"
with_items: "{{ module_result.archived }}"

How can I traverse nested lists in Ansible?

I have a list of devices, each of which has a varying list of attributes that must be created on the devices, one at a time. Is there a way to build a nested set of loops to do this? The with_nested construct applies every attribute to every device; I need only a single attribute per device per call.
This playbook demonstrates what I have tried (Ansible 2.7.1, Python 2.7.1):
- name: Test nested list traversal
hosts: localhost
connection: local
vars:
Stuff:
- Name: DeviceA
Info: AInfo
Values:
- ValueA1
- ValueA2
- Name: DeviceB
Info: BInfo
Values:
- ValueB1
- ValueB2
- ValueB3
tasks:
- name: Loop test
debug:
msg: "{{ item.Name }},{{ item.Info }}, {{ item.Values }}"
with_items:
- "{{ Stuff }}"
What I get: (One call per device, containing all attributes at once)
ok: [localhost] => (item={u'Info': u'AInfo', u'Values': [u'ValueA1', u'ValueA2'], u'Name': u'DeviceA'}) =>
msg: DeviceA,AInfo, [u'ValueA1', u'ValueA2']
ok: [localhost] => (item={u'Info': u'BInfo', u'Values': [u'ValueB1', u'ValueB2', u'ValueB3'], u'Name': u'DeviceB'}) =>
msg: DeviceB,BInfo, [u'ValueB1', u'ValueB2', u'ValueB3']
What I want (each msg represents a separate operation to be performed on the device with just that one attribute)
msg: DeviceA, AInfo, ValueA1
msg: DeviceA, AInfo, ValueA2
msg: DeviceB, BInfo, ValueB1
msg: DeviceB, BInfo, ValueB2
msg: DeviceB, BInfo, ValueB3
You can get what you want using the subelements filter:
---
- hosts: localhost
gather_facts: false
vars:
Stuff:
- Name: DeviceA
Info: AInfo
Values:
- ValueA1
- ValueA2
- Name: DeviceB
Info: BInfo
Values:
- ValueB1
- ValueB2
- ValueB3
tasks:
- debug:
msg: "{{ item.0.Name }}, {{ item.0.Info }}, {{ item.1 }}"
loop: "{{ Stuff|subelements('Values') }}"
loop_control:
label: "{{ item.0.Name }}"
Running the above playbook gets you:
PLAY [localhost] ******************************************************************************************************************************************************************************
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => (item=DeviceA) => {
"msg": "DeviceA, AInfo, ValueA1"
}
ok: [localhost] => (item=DeviceA) => {
"msg": "DeviceA, AInfo, ValueA2"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB1"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB2"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB3"
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0

Resources