Imagine this playbook:
---
- hosts: my_host
gather_facts: false
vars:
mylist:
- abc
- def
- xyz
tasks:
- name: dummy task 1
shell: "echo 'task 1 {{ item }}'"
loop: "{{ my_list }}"
- name: dummy task 2
shell: "echo 'task 2 {{ item }}'"
loop: "{{ my_list }}"
Would it be possible to let Ansible loop over the tasks, one by one, based on the items defined in mylist?
The desired result would be:
task 1 abc
task 2 abc
task 1 def
task 2 def
task 1 xyz
task 2 xyz
Place the tasks in a separate file (e.g., tasks.yaml) and then loop over an include_tasks task.
If we have a file tasks.yaml with this content:
- name: task1
debug:
msg: "task 1 {{ item }}"
- name: task2
debug:
msg: "task 2 {{ item }}"
And playbook.yaml with this content:
- hosts: localhost
gather_facts: false
tasks:
- include_tasks: tasks.yaml
loop:
- abc
- def
- xyz
Then running ansible-playbook playbook.yaml produces as output:
PLAY [localhost] ***************************************************************
TASK [include_tasks] ***********************************************************
included: /home/lars/tmp/ansible/tasks.yaml for localhost => (item=abc)
included: /home/lars/tmp/ansible/tasks.yaml for localhost => (item=def)
included: /home/lars/tmp/ansible/tasks.yaml for localhost => (item=xyz)
TASK [task1] *******************************************************************
ok: [localhost] => {
"msg": "task 1 abc"
}
TASK [task2] *******************************************************************
ok: [localhost] => {
"msg": "task 2 abc"
}
TASK [task1] *******************************************************************
ok: [localhost] => {
"msg": "task 1 def"
}
TASK [task2] *******************************************************************
ok: [localhost] => {
"msg": "task 2 def"
}
TASK [task1] *******************************************************************
ok: [localhost] => {
"msg": "task 1 xyz"
}
TASK [task2] *******************************************************************
ok: [localhost] => {
"msg": "task 2 xyz"
}
PLAY RECAP *********************************************************************
localhost : ok=9 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Related
If, during the execution of the playbook, we change the host file in host_vars (i.e. add a new variable), how then can we get this variable in hostvars in the current execution of the playbook? When you run it again, it appears in hostvars.
UPDATE 01:
Here's an example, it doesn't work (
The task Debug 3 should display test_1 instead of VARIABLE IS NOT DEFINED!
- name: Test
hosts: mon
tasks:
- name: Debug 1
debug:
var: hostvars.mon.test_1
- name: Add vars for host_vars
delegate_to: 127.0.0.1
blockinfile:
path: "{{ inventory_dir }}/host_vars/{{ inventory_hostname }}.yml"
marker: "# {mark}: {{ item.key }}"
block: |
{{ item.key }}: {{ item.value }}
with_dict:
- {test_1: "test_1"}
- name: Debug 2
debug:
var: hostvars.mon.test_1
- name: Clear facts
meta: clear_facts
- name: Refresh inventory
meta: refresh_inventory
- name: Setup
setup:
- name: Debug 3
debug:
var: hostvars.mon.test_1
Result:
PLAY [Test] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [mon]
TASK [Debug 1] *****************************************************************
ok: [mon] => {
"hostvars.mon.test_1": "VARIABLE IS NOT DEFINED!"
}
TASK [Add vars for host_vars] **************************************************
changed: [mon -> 127.0.0.1] => (item={'key': 'test_1', 'value': 'test_1'})
TASK [Debug 2] *****************************************************************
ok: [mon] => {
"hostvars.mon.test_1": "VARIABLE IS NOT DEFINED!"
}
TASK [Setup] *******************************************************************
ok: [mon]
TASK [Debug 3] *****************************************************************
ok: [mon] => {
"hostvars.mon.test_1": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP *********************************************************************
mon : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
On restart:
PLAY [Test] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [mon]
TASK [Debug 1] *****************************************************************
ok: [mon] => {
"hostvars.mon.test_1": "test_1"
}
TASK [Add vars for host_vars] **************************************************
ok: [mon -> 127.0.0.1] => (item={'key': 'test_1', 'value': 'test_1'})
TASK [Debug 2] *****************************************************************
ok: [mon] => {
"hostvars.mon.test_1": "test_1"
}
TASK [Setup] *******************************************************************
ok: [mon]
TASK [Debug 3] *****************************************************************
ok: [mon] => {
"hostvars.mon.test_1": "test_1"
}
PLAY RECAP *********************************************************************
mon : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Maybe there is a way to change hostvars manually in the process?
You can ask Ansible to reread inventory (including host_vars). Generally I'd say that changing inventory on-fly is a code smell, but there are few valid cases.
- name: Refreshing inventory, SO copypaste
meta: refresh_inventory
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
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
Let's consider following output of some command (it is debug of stdout_lines):
- command: "some command"
register: output
- debug:
msg: "{{ output.stdout_lines }}"
"msg": [
"X",
"Y",
"Z",
" 1 some_word1",
" 1 some_word2",
" 1 some_word3",
" 1 some_word4"
]
The output is registered in variable output using command task. Now, I need to parse it into a list of the form: [some_word1,some_word2,some_word3,some_word4].
Please keep in mind that output can contain more or less (even 0) than 4 words.
When it comes to "X","Y","Z" they are constant (always three lines) so I can skip it using:
- debug:
msg: "{{ output.stdout_lines[3:] }}"
However, I don't know how to deal with leading spaces and digit.
Could you give me some clues, please?
Knowing a string is "just" a list of char in python, if your output is always
/[[:space:]]+[[:digit:]] (.*)/
and never
/[[:space:]]+[[:digit:]]+ (.*)/
e.g. 1 some_word1 or 9 some_word9 but not 10 some_word10
Then you could apply the trim filter and just reuse your same list index trick, ending with this jinja expression:
---
- hosts: local
vars:
"msg": [
"X",
"Y",
"Z",
" 1 some_word1",
" 1 some_word2",
" 1 some_word3",
" 1 some_word4"
]
tasks:
- debug:
msg: "{{ (item | trim())[2:] }}" # after trimming the item, we just ignore the first two char as you did for your three first output lines
with_items: "{{ msg[3:] }}"
This outputs:
/data/playbooks # ansible-playbook so.yml
PLAY [local] *******************************************************************
TASK [Gathering Facts] *********************************************************
ok: [host1]
TASK [debug] *******************************************************************
ok: [host1] => (item= 1 some_word1) => {
"msg": "some_word1"
}
ok: [host1] => (item= 1 some_word2) => {
"msg": "some_word2"
}
ok: [host1] => (item= 1 some_word3) => {
"msg": "some_word3"
}
ok: [host1] => (item= 1 some_word4) => {
"msg": "some_word4"
}
PLAY RECAP *********************************************************************
host1 : ok=2 changed=0 unreachable=0 failed=0
Now if you have the second form, or if you want to make it more the bash way, then you could change your command to a shell – because shell accepts piping when command does not – and pipe your output to awk:
---
- hosts: local
tasks:
- shell: printf "X\nY\nZ\n 1 some_word1\n 1 some_word2\n 1 some_word3\n 1 some_word4" | awk '{print $2}'
register: output
- debug:
msg: "{{ output.stdout_lines[3:] }}"
This outputs:
/data/playbooks # ansible-playbook so.yml
PLAY [local] *******************************************************************
TASK [Gathering Facts] *********************************************************
ok: [host1]
TASK [shell] *******************************************************************
changed: [host1]
TASK [debug] *******************************************************************
ok: [host1] => {
"msg": [
"some_word1",
"some_word2",
"some_word3",
"some_word4"
]
}
PLAY RECAP *********************************************************************
host1 : ok=3 changed=1 unreachable=0 failed=0
Ansible v2.6.3
I have the simple task, which gets the AWS ARNs in my jenkins ECS cluster
tasks:
- command: aws ecs list-container-instances --cluster jenkins
register: jenkins_ecs_containers
- debug: var=jenkins_ecs_containers.stdout
and has the following output
TASK [debug] *******************************************************************
ok: [localhost] => {
"jenkins_ecs_containers.stdout": {
"containerInstanceArns": [
"arn:aws:ecs:us-east-1:arn0",
"arn:aws:ecs:us-east-1:arn1"
]
}
}
How can I iterate over the ARNs? I tried
- debug: var=item
with_items: jenkins_ecs_containers.stdout.containerInstanceArns
gives
TASK [debug] *******************************************************************
ok: [localhost] => (item=jenkins_ecs_containers.stdout.containerInstanceArns) => {
"item": "jenkins_ecs_containers.stdout.containerInstanceArns"
}
or
- debug: var=item
with_items: "{{ jenkins_ecs_containers.stdout.containerInstanceArns }}"
gives
TASK [debug] *******************************************************************
fatal: [localhost]: FAILED! => {"msg": "'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'containerInstanceArns'"}
to retry, use: --limit #/Users/cfouts/git-repos/ansible/playbooks/loop.retry
Thanks!
I created a file with your output. So I used set_fact. Otherwise, it's just a string, not a JSON object:
tasks:
- command: cat files/stdout.txt
register: result
- debug: var=result.stdout
- set_fact:
jenkins_ecs_containers: "{{ result.stdout }}"
- debug:
msg: "{{ item }}"
with_items: "{{ jenkins_ecs_containers.containerInstanceArns }}"
This gave me the following output:
PLAY [localhost] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [command] *****************************************************************
changed: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"result.stdout": {
"containerInstanceArns": [
"arn:aws:ecs:us-east-1:arn0",
"arn:aws:ecs:us-east-1:arn1"
]
}
}
TASK [set_fact] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => (item=None) => {
"msg": "arn:aws:ecs:us-east-1:arn0"
}
ok: [localhost] => (item=None) => {
"msg": "arn:aws:ecs:us-east-1:arn1"
}
PLAY RECAP *********************************************************************
localhost : ok=5 changed=1 unreachable=0 failed=0
You can iterate over like this:
- debug:
msg: "{{ item[1] }}"
with_subelements:
- "{{ jenkins_ecs_containers }}"
- containerInstanceArns
Go through this link, it will make it clearer.