I am trying to define a template in Ansible Tower, where I want to extract the id for the Active Controller in Kafka Broker and then use this value in another template / task that will perform the rolling restart but will make sure the active controller is started last
When I run this Ansible task
- name: Find active controller
shell: '/bin/zookeeper-shell 192.168.129.227 get /controller'
register: resultAC
I get the below result. I want to extract the brokerid and assign the value of 2 to a variable that can be used in a different task in the same template or pass it to another template when the templates are part of a workflow definition.
I tried using resultAC.stdout_lines[5].brokerid but that does not work.
The structure of resultAC:
{
"resultAC": {
"stderr_lines": [],
"changed": true,
"end": "2020-08-19 07:36:01.950347",
"stdout": "Connecting to 192.168.129.227\n\nWATCHER::\n\nWatchedEvent state:SyncConnected type:None path:null\n{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}",
"cmd": "/bin/zookeeper-shell 192.168.129.227 get /controller",
"failed": false,
"delta": "0:00:02.843972",
"stderr": "",
"rc": 0,
"stdout_lines": [
"Connecting to 192.168.129.227",
"",
"WATCHER::",
"",
"WatchedEvent state:SyncConnected type:None path:null",
"{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}"
],
"start": "2020-08-19 07:35:59.106375"
},
"_ansible_verbose_always": true,
"_ansible_no_log": false,
"changed": false
}
Because your JSON is just part of a list of strings, it is not parsed or considered as a JSON.
You will have to use the Ansible filter from_json in order to parse it back to a dictionary.
Given the playbook:
- hosts: all
gather_facts: no
vars:
resultAC:
stdout_lines:
- "Connecting to 192.168.129.227"
- ""
- "WATCHER::"
- ""
- "WatchedEvent state:SyncConnected type:None path:null"
- "{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}"
tasks:
- debug:
msg: "{{ (resultAC.stdout_lines[5] | from_json).brokerid }}"
This gives the recap:
PLAY [all] *************************************************************************************************************************************************************
TASK [debug] ***********************************************************************************************************************************************************
ok: [localhost] => {
"msg": "2"
}
PLAY RECAP *************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Going further, maybe I would select and match the JSON in the stdout_lines list, just in case it is not always at the sixth line:
- hosts: all
gather_facts: no
vars:
resultAC:
stdout_lines:
- "Connecting to 192.168.129.227"
- ""
- "WATCHER::"
- ""
- "WatchedEvent state:SyncConnected type:None path:null"
- "{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}"
tasks:
- debug:
msg: "{{ (resultAC.stdout_lines | select('match','{.*\"brokerid\":.*}') | first | from_json).brokerid }}"
Related
Using Ansible v2.9.12
Question: I'd like Ansible to fail/stop the play when a task fails, when multiple hosts execute the task. In that sense, Ansible should abort the task from further execution. The configuration should work in a role, so using serial, or using different plays, is not possible.
Example ;
- hosts:
- host1
- host2
- host3
any_errors_fatal: true
tasks:
- name: always fail
shell: /bin/false
throttle: 1
Provides ;
===== task | always fail =======
host1: fail
host2: fail
host3: fail
Meaning, the task is still executed on the second host and third host. I'd desire the whole play to fail/stop, once a task fails on a host. When the task fails on the last host, Ansible should abort as well.
Desired outcome ;
===== task | always fail =======
host1: fail
host2: not executed/skipped, cause host1 failed
host3: not executed/skipped, cause host1 failed
As you can see, I've fiddled around with error handling, but without prevail.
Background info: I've been developing an idempotent Ansible role for mysql. It is possible to setup a cluster with multiple hosts. The role also supports adding an arbiter.
The arbiter does not has the mysql application installed, but the host is still required in the play.
Now, imagine three hosts. Host1 is the arbiter, host2 and host3 have mysql installed, setup in a cluster. The applications are setup by the Ansible role.
Now, Ansible executes the role for a second/third/fourth/whatever time, and changes a config setting of mysql. Mysql needs a rolling restart. Usually, one writes some thing along the lines of:
- template:
src: mysql.j2
dest: /etc/mysql
register: mysql_config
when: mysql.role != 'arbiter'
- service:
name: mysql
state: restarted
throttle: 1
when:
- mysql_config.changed
- mysql.role != 'arbiter'
The downside of this Ansible configuration, is that if mysql fails to start on host2 due to whatever reason, Ansible will also restart mysql on host3. And that is undesired, because if mysql fails on host3 as well, then the cluster is lost. So, for this specific task I'd like Ansible to stop/abort/skip other tasks if mysql has failed to start on a single host in the play.
Ok, this works:
# note that test-multi-01 set host_which_is_skipped: true
---
- hosts:
- test-multi-01
- test-multi-02
- test-multi-03
tasks:
- set_fact:
host_which_is_skipped: "{{ inventory_hostname }}"
when: host_which_is_skipped
- shell: /bin/false
run_once: yes
delegate_to: "{{ item }}"
loop: "{{ ansible_play_hosts }}"
when:
- item != host_which_is_skipped
- result is undefined or result is not failed
register: result
- meta: end_play
when: result is failed
- debug:
msg: Will not happen
When the shell command is set to /bin/true, the command is executed on host2 and host3.
One way to solve this would be to run the playbook with serial: 1. That way, the tasks are executed serially on the hosts and as soon as one task fails, the playbook terminates:
- name: My playbook
hosts: all
serial: 1
any_errors_fatal: true
tasks:
- name: Always fail
shell: /bin/false
In this case, this results in the task only being executed on the first host. Note that there is also the order clause, with which you can also control the order in which hosts are run: https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#hosts-and-users
Disclaimer: most of the credit of the fully working part of this answer is going to #Tomasz Klosinski's answer on Server Fault.
Here is for a partially working idea, that only falls short of one host.
For the demo, I purposely increased my hosts number to 5 hosts.
The idea is based on the special variables ansible_play_batch and ansible_play_hosts_all that are described in the above mentioned document page as:
ansible_play_hosts_all
List of all the hosts that were targeted by the play
ansible_play_batch
List of active hosts in the current play run limited by the serial, aka ‘batch’. Failed/Unreachable hosts are not considered ‘active’.
The idea, coupled with your trial at using throttle: 1 should work, but fail short of one host, executing on host2 when it should skip it.
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- shell: /bin/false
when: "ansible_play_batch | length == ansible_play_hosts_all | length"
throttle: 1
This yields the recap:
PLAY [all] ***********************************************************************************************************
TASK [shell] *********************************************************************************************************
fatal: [host1]: FAILED! => {"changed": true, "cmd": "/bin/false", "delta": "0:00:00.003915", "end": "2020-09-06 22:09:16.550406", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:09:16.546491", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
fatal: [host2]: FAILED! => {"changed": true, "cmd": "/bin/false", "delta": "0:00:00.004736", "end": "2020-09-06 22:09:16.844296", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:09:16.839560", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
skipping: [host3]
skipping: [host4]
skipping: [host5]
PLAY RECAP ***********************************************************************************************************
host1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
host2 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
host3 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
host4 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
host5 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Looking further on that, I landed on this answer of Server Fault, and this looks to be the right idea to craft your solution.
Instead of going the normal way, the idea is to delegate everything from the first host with a loop on all targeted hosts of the play, because, in a loop, you are then able to access the registered fact of the previous host in an easy manner, as long as your register it.
So here is the playbook:
- hosts: all
gather_facts: no
tasks:
- shell: /bin/false
loop: "{{ ansible_play_hosts }}"
register: failing_task
when: "failing_task | default({}) is not failed"
delegate_to: "{{ item }}"
run_once: true
This would yield the recap:
PLAY [all] ***********************************************************************************************************
TASK [shell] *********************************************************************************************************
failed: [host1 -> host1] (item=host1) => {"ansible_loop_var": "item", "changed": true, "cmd": "/bin/false", "delta": "0:00:00.003706", "end": "2020-09-06 22:18:23.822608", "item": "host1", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:18:23.818902", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
skipping: [host1] => (item=host2)
skipping: [host1] => (item=host3)
skipping: [host1] => (item=host4)
skipping: [host1] => (item=host5)
NO MORE HOSTS LEFT ***************************************************************************************************
PLAY RECAP ***********************************************************************************************************
host1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
And just for the sake of proving it works as intended, altering it to make the host2 fail specifically, with the help of failed_when:
- hosts: all
gather_facts: no
tasks:
- shell: /bin/false
loop: "{{ ansible_play_hosts }}"
register: failing_task
when: "failing_task | default({}) is not failed"
delegate_to: "{{ item }}"
run_once: true
failed_when: "item == 'host2'"
Yields the recap:
PLAY [all] ***********************************************************************************************************
TASK [shell] *********************************************************************************************************
changed: [host1 -> host1] => (item=host1)
failed: [host1 -> host2] (item=host2) => {"ansible_loop_var": "item", "changed": true, "cmd": "/bin/false", "delta": "0:00:00.004226", "end": "2020-09-06 22:20:38.038546", "failed_when_result": true, "item": "host2", "msg": "non-zero return code", "rc": 1, "start": "2020-09-06 22:20:38.034320", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
skipping: [host1] => (item=host3)
skipping: [host1] => (item=host4)
skipping: [host1] => (item=host5)
NO MORE HOSTS LEFT ***************************************************************************************************
PLAY RECAP ***********************************************************************************************************
host1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
I want to check the ip on the server where ip list i have in the json file as below,
cat /tmp/iplist.json
[
"10.10.10.182",
"182.10.10.2",
"192.168.200.2"
]
now the condition is only one ip exist on the system so i was executing the loop to store the only the success output in variable but i am not able to do that does any one knows how can i do this
here is my playbook
---
- name: Load values from json file
hosts: localhost
gather_facts: false
vars:
ip: "{{ lookup('file', '/tmp/iplist.json') | from_json }}"
tasks:
- name: Loop over imported iplist
shell: ip a | grep {{ item }}
loop: "{{ ip }}"
changed_when: true
register: echo
- debug:
msg: "{{ echo }}"
And this how it getting failed error
PLAY [Load values from json file] *************************************************************************************************************************
TASK [Loop over imported iplist] *******************************************************************************************************************************
changed: [localhost] => (item=10.10.10.182)
failed: [localhost] (item=182.10.10.2) => {"ansible_loop_var": "item", "changed": true, "cmd": "ip a | grep 182.10.10.2", "delta": "0:00:00.012178", "end": "2020-05-09 11:30:06.919913", "item": "182.10.10.2", "msg": "non-zero return code", "rc": 1, "start": "2020-05-09 11:30:06.907735", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
failed: [localhost] (item=192.168.200.2) => {"ansible_loop_var": "item", "changed": true, "cmd": "ip a | grep 192.168.200.2", "delta": "0:00:00.029234", "end": "2020-05-09 11:30:07.178768", "item": "192.168.200.2", "msg": "non-zero return code", "rc": 1, "start": "2020-05-09 11:30:07.149534", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
PLAY RECAP *****************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
When you enable gather_facts: true the variable ansible_all_ipv4_addresses will keep the list of all IPv4 addresses of the host. Use intersect to find common items. For example
- debug:
msg: "{{ ansible_all_ipv4_addresses | intersect(ip) }}"
I am running a command to get the status of tomcat and registering it in a variable. How do i extract the specific output of that command and put it in a variable to check further
Play -
- name: Check the State og tomcat service
shell: "svcs tomcat"
register: tomcat_status
- name: Show captured processes
debug:
msg: "{{ tomcat_status.stdout_lines|list }}"
The output of the above is -
server1 ok: {
"changed": false,
"msg": [
"STATE STIME FMRI",
"online 20:11:48 svc:/network/tomcat:tomcat"
]
}
How do i extract the value of STATE here? I want to know if it's online or disabled or shutdown etc.
NOTE - Output with -vvvv
server1 done: {
"changed": true,
"cmd": "svcs tomcat",
"delta": "0:00:00.025711",
"end": "2020-05-11 12:43:43.323017",
"invocation": {
"module_args": {
"_raw_params": "svcs tomcat",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": false
}
},
"rc": 0,
"start": "2020-05-11 12:43:43.297306",
"stderr": "",
"stderr_lines": [],
"stdout": "STATE STIME FMRI\nonline 20:11:48 svc:/network/tomcat:tomcat",
"stdout_lines": [
"STATE STIME FMRI",
"online 20:11:48 svc:/network/tomcat:tomcat"
]
}
Based on your debug about, tomcat_status.stdout_lines is a list (you don't need the |list filter here because it's already a list) that looks like:
["STATE STIME FMRI", "online 20:11:48 svc:/network/tomcat:tomcat"]
To get the STATE value, you need the first field from the second line. So:
- set_fact:
tomcat_state: "{{ tomcat_status.stdout_lines.1.split().0 }}"
That takes the second line (tomcat_status.stdout_lines.1) then splits it on whitespace (.split()), and then takes the first value (.0).
Here's a complete test:
- hosts: localhost
vars:
tomcat_status:
stdout_lines:
- "STATE STIME FMRI"
- "online 20:11:48 svc:/network/tomcat:tomcat"
tasks:
- set_fact:
tomcat_state: "{{ tomcat_status.stdout_lines.1.split().0 }}"
- debug:
var: tomcat_state
Running that playbook results in:
PLAY [localhost] *****************************************************************************
TASK [set_fact] ******************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] => {
"tomcat_state": "online"
}
PLAY RECAP ***********************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
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}}"
I would like to fetch first few character from a registered variable. Can somebody please suggest me how to do that.
- hosts: node1
gather_facts: False
tasks:
- name: Check Value mango
shell: cat /home/vagrant/mango
register: result
- name: Display Result In Loop
debug: msg="Version is {{ result.stdout[5] }}"
The above code displays the fifth character rather first 5 characters of the registered string.
PLAY [node1] ******************************************************************
TASK: [Check Value mango] *****************************************************
changed: [10.200.19.21] => {"changed": true, "cmd": "cat /home/vagrant/mango", "delta": "0:00:00.003000", "end": "2015-08-19 09:29:58.229244", "rc": 0, "start": "2015-08-19 09:29:58.226244", "stderr": "", "stdout": "d3aa6131ec1a2e73f69ee150816265b5617d7e69", "warnings": []}
TASK: [Display Result In Loop] ************************************************
ok: [10.200.19.21] => {
"msg": "Version is 1"
}
PLAY RECAP ********************************************************************
10.200.19.21 : ok=2 changed=1 unreachable=0 failed=0
You can work with ranges:
- name: Display Result In Loop
debug: msg="Version is {{ result.stdout[:5] }}"
This will print the first 5 characters.
1:5 would print the characters 2 to 5, skipping the 1st char.
5: Would skip the the first 5 chars and print the rest of the string