Issue 1:
ISSUE TYPE

Feature Idea

COMPONENT NAME

lib/ansible/playbook

ANSIBLE VERSION

N/A

SUMMARY

This feature request is quite simple : with so many issues linked to the crucial "any_errors_fatal" property on plays (even in Ansible 2.4), please add an option so that in extreme conditions a programmed exit can be used. This should set a "failed" status on all hosts of the playbook of course.

In my case we use three playbooks:

stop

deploy

start

Each playbook runs against several hosts and is included as a play in a "stop-deploy-restart" playbook.

# stop-deploy-restart.yml (2.3-style)
- include: "stop.yml"
- include: "deploy.yml"
- include: "start.yml"

But we want to deploy only if every host has stopped successfully. If any host fails the "stop" play, we'd like to fail the wrapping "stop-deploy-restart" playbook.

In theory we could use "any_errors_fatal", but as I said its behaviour is clearly not bug-free.

And for the moment we can touch a file in the "post_tasks" part of "stop" in order to keep track of which host has successfully stopped, but there seems to be no mechanism in Ansible that allows us to prevent the plays to continue. I'd like somthing like this:

# stop-deploy-restart.yml (2.3-style)
- include: "stop.yml"
- meta: exit
when: something_went_wrong # makes every hosts fail AND skips everything else in the playbook
- include: "deploy.yml"
- include: "start.yml"

And of course when "any_errors_fatal" is repaired that won't be the nice way to write things. But it could still be usefull.

Thanks in advance.

Issue 2:
ISSUE TYPE

Bug Report

COMPONENT NAME

task

ANSIBLE VERSION

2.2.1.0

CONFIGURATION

[defaults]
host_key_checking = False
host_key_checking = False
record_host_keys = False
remote_user	= "ubuntu"
timeout	= 600
nocows	= 1
forks = 25
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True

OS / ENVIRONMENT

ubuntu 16.04

SUMMARY

missing host in a task and duplicated on the next in the same playbook/role

STEPS TO REPRODUCE

- name: Check if one app have failed <- missing host
set_fact: one_fail=true
delegate_to: "{{ groups.BASTION[0] }}"
delegate_facts: true
when: item.rc != 0
with_items: "{{ custom_script.results | default([]) }}"

- name: Delete all apps if one script fails <- dupliated host
file: path={{ destpath }}/{{ item.item.stdout.split('@')[0] }}/releases/{{ item.item.stdout }} state=absent
when: hostvars[groups.BASTION[0]].one_fail | default([]) and
exit_fast
with_items: "{{ custom_script.results | default([]) }}"

EXPECTED RESULTS

no missing or duplicated hosts

ACTUAL RESULTS

TASK [deployer : Check if one app have failed] *********************************
ok: [172.16.16.128 -> 172.16.17.1] => (item={u'_ansible_parsed': True, u'cmd': u'sudo -E ...
skipping: [172.16.16.135] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.138] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.139] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.102] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.140] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.149] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.150] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...
skipping: [172.16.16.146] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout': ...

TASK [deployer : Delete all apps if one script fails] **************************
skipping: [172.16.16.148] => (item={u'_ansible_parsed': True, u'changed': True, u'std <- DUPLICATED
skipping: [172.16.16.145] => (item={u'_ansible_parsed': True, u'changed': True, u'std <- DUPLICATED
skipping: [172.16.16.128] => (item={u'_ansible_parsed': True, u'cmd': u'sudo -E - ...
skipping: [172.16.16.139] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.138] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.135] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.140] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.102] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.145] => (item={u'_ansible_parsed': True, u'changed': True, u'std <- DUPLICATED
skipping: [172.16.16.146] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.149] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.150] => (item={u'_ansible_parsed': True, u'changed': True, u'stdout' ...
skipping: [172.16.16.148] => (item={u'_ansible_parsed': True, u'changed': True, u'std <- DUPLICATED