In Ansible, how can I iterate over stdout with an array? - ansible

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.

Related

Ansible - loop tasks one by one based on list

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

How to skip a hostname from ansible inventory file?

My ansible inventory file has the following entry
[non_prod_servers]
oracle[1:13]
How can I eliminate hosts "oracle7" and "oracle10" from the above specification, without having to create the following entries?
[non_prod_servers]
oracle[1:6]
oracle[8:9]
oracle[11:13]
Essentially, looking for an elegant solution than what I have come up with.
Thanks in advance.
FR
IMHO, the functionality, you're looking for, is not available. You can use Python slicing inside the inventory. The inventory patterns do not apply inside the inventory file.
If you need it for automation, i.e. you want to control the process by a couple of variables the inventory and playbook below create the group dynamically
shell> cat hosts
[non_prod_servers]
localhost
[non_prod_servers:vars]
_name=oracle
_from=1
_to=13
_deny=[7,10]
shell> cat playbook.yml
---
- hosts: non_prod_servers
gather_facts: false
tasks:
- add_host:
hostname: "{{ _name }}{{ item }}"
groups: non_prod_servers_dyn
loop: "{{ range(_from, _to + 1)|difference(_deny) }}"
- hosts: non_prod_servers_dyn
gather_facts: false
tasks:
- debug:
var: ansible_play_hosts_all
run_once: true
gives
shell> ansible-playbook -i hosts playbook.yml
PLAY [non_prod_servers] *************************************************
TASK [add_host] *********************************************************
ok: [localhost] => (item=1)
ok: [localhost] => (item=2)
ok: [localhost] => (item=3)
ok: [localhost] => (item=4)
ok: [localhost] => (item=5)
ok: [localhost] => (item=6)
ok: [localhost] => (item=8)
ok: [localhost] => (item=9)
ok: [localhost] => (item=11)
ok: [localhost] => (item=12)
ok: [localhost] => (item=13)
PLAY [non_prod_servers_dyn] **********************************************
TASK [debug] *************************************************************
ok: [oracle1] =>
ansible_play_hosts_all:
- oracle1
- oracle2
- oracle3
- oracle4
- oracle5
- oracle6
- oracle8
- oracle9
- oracle11
- oracle12
- oracle13

Ansible. Updating the hostvars variable

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

ansible find first undefined value in list

I have a list in ansible and I want to find the first "unused" item in that list.
Example list:
item001
item002
item004
item005
item100
item101
The prefix numbers in the items could be up to 999
In the example above the result should be item003.
Here is an example playbook of what I want
---
- name: Test
connection: local
hosts: localhost
vars:
my_list: []
list1:
- item107
- item002
- item004
- item001
- item007
- item101
- item604
tasks:
- name: Initialize a dummy list
set_fact:
my_list: "{{ my_list|sort }} + [ '{{ item }}' ]"
with_sequence: start=1 end=19 format=item%.3d
- name: print first unused value in my_list
debug:
msg: "{{ (my_list | difference(list1))[0] }}"
output:
PLAY [Test] *******************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Initialize a dummy list] ************************************************************************************************************************************************************************************
ok: [localhost] => (item=item001)
ok: [localhost] => (item=item002)
ok: [localhost] => (item=item003)
ok: [localhost] => (item=item004)
ok: [localhost] => (item=item005)
ok: [localhost] => (item=item006)
ok: [localhost] => (item=item007)
ok: [localhost] => (item=item008)
ok: [localhost] => (item=item009)
ok: [localhost] => (item=item010)
ok: [localhost] => (item=item011)
ok: [localhost] => (item=item012)
ok: [localhost] => (item=item013)
ok: [localhost] => (item=item014)
ok: [localhost] => (item=item015)
ok: [localhost] => (item=item016)
ok: [localhost] => (item=item017)
ok: [localhost] => (item=item018)
ok: [localhost] => (item=item019)
TASK [print first unused value in my_list] ************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "item003"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
But if I change with_sequence: start=1 end=19 to with_sequence: start=1 end=999 the tasks takes very long time and also prints 999 lines in the output I really would love to not see.
This actually worked better, didn't print any large output and was much quicker:
- name: Initialize a dummy list
set_fact:
my_list: "{{ lookup('sequence', 'start=1 count=999 format=item%.3d', wantlist=True) }}"

How to access a programmatically constructed ansible variable?

I have constructed an ansible variable using two other defined ansible variables. The constructed variable is defined in the vars/main.yml and I want to access the defined value in vars/main.yml.
vars/main.yml
---
var1_var2: "some value"
Now, I construct the variable
---
- name: Construct and get the value
hosts: localhost
tasks:
- include_vars: "vars/main.yml"
- set_fact:
variable1: "var1"
variable2: "var2"
- set_fact:
final_variable: "{{ variable1 }}_{{ variable2 }}"
- set_fact:
ultimate_variable: "{{ final_variable }}"
If I run the playbook with -vvv flag, I can see that ultimate_variable sets to var1_var2 while I want to get the value defined in the vars/main.yml
i.e., some value
TASK [set_fact]
ok: [localhost] => {
"ansible_facts": {
"variable1": "var1",
"variable2": "var2"
},
"changed": false,
"failed": false
}
TASK [set_fact] task path: /home/ubuntu/test.yml:78
ok: [localhost] => {
"ansible_facts": {
"final_variable": "var1_var2"
},
"changed": false,
"failed": false
}
TASK [set_fact]
ok: [localhost] => {
"ansible_facts": {
"ultimate_variable": "var1_var2"
},
"changed": false,
"failed": false
}
updated answer:
use the lookup plugin to do the double replacement:
ultimate_variable: "{{ lookup('vars', '{{final_variable}}') }}"
playbook:
- include_vars: "vars/main.yml"
- set_fact:
variable1: "var1"
variable2: "var2"
- set_fact:
final_variable: "{{ variable1 }}_{{ variable2 }}"
- set_fact:
ultimate_variable: "{{ lookup('vars', '{{final_variable}}') }}"
- debug:
var: ultimate_variable
output:
PLAY [localhost] ****************************************************************************************************************************************************************************************************
TASK [include_vars] *************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"ultimate_variable": "some value"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
hope it helps.
You could also skip the intermediary variable definition:
- include_vars: "vars/main.yml"
- set_fact:
variable1: "var1"
variable2: "var2"
- set_fact:
ultimate_variable: "{{ lookup('vars', variable1 ~ '_' ~ variable2) }}"
- debug:
var: ultimate_variable

Resources