Repository: ansible/ansible

Issue:
in 2.0.1.0 and later, skipped plays impact  gathering=smart
<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE

<!--- Pick one below and delete the rest: -->
- Bug Report
##### ANSIBLE VERSION

```
ansible 2.0.1.0
  config file = 
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

N/A
##### OS / ENVIRONMENT

N/A
##### SUMMARY

When `ANSIBLE_GATHERING` is `smart`, a skipped play targeting a
particular host will cause Ansible to not collect facts for that host.
##### STEPS TO REPRODUCE

Use this playbook:

```
- hosts: localhost
  tags:
    - sampletag
  tasks:
    - debug:
        msg: "this play can be skipped with --skip-tags sampletag"

- hosts: localhost
  tasks:
    - assert:
        that: ansible_distribution is defined
```

Compare:

```
ANSIBLE_GATHERING=implicit ansible-playbook playbook.yml \
  --skip-tags sampletag
```

With:

```
ANSIBLE_GATHERING=smart ansible-playbook playbook.yml \
  --skip-tags sampletag
```

In version 2.0.0.0, you will see:

```
PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [localhost]

PLAY ***************************************************************************

TASK [assert] ******************************************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0   
```

But in version 2.0.1.0 and 2.0.2.0 you will see:

```
PLAY ***************************************************************************

PLAY ***************************************************************************

TASK [assert] ******************************************************************
fatal: [localhost]: FAILED! => {"assertion": "ansible_distribution is defined", "changed": false, "evaluated_to": false, "failed": true}

NO MORE HOSTS LEFT *************************************************************
        to retry, use: --limit @playbook.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1   
```

That is, a play that is completely skipped and produces no output
during the `ansible-playbook` and _does not run the setup module_
still results in Ansible not running `setup` in subsequent tasks.
##### EXPECTED RESULTS

I expect Ansible to connect to the target host and gather facts at
least once during the play.
##### ACTUAL RESULTS

No facts are gathered.



Discussion:
@jimi-c This appears to have been introduced by 5587b08335f223fff64f54ced6f2790b3d6ee6f0.

I think the solution is to _not_ set `gathered_facts=True` in `_get_next_task_from_state` unless there are tasks to be executed.  For `devel`, I think that means:

```
diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py
index 3ab2e28..b5fc8bf 100644
--- a/lib/ansible/executor/play_iterator.py
+++ b/lib/ansible/executor/play_iterator.py
@@ -295,10 +295,10 @@ class PlayIterator:
                         setup_block = self._blocks[0]
                         if setup_block.has_tasks() and len(setup_block.block) > 0:
                             task = setup_block.block[0]
-                        if not peek:
-                            # mark the host as having gathered facts, because we're
-                            # returning the setup task to be executed
-                            host.set_gathered_facts(True)
+                            if not peek:
+                                # mark the host as having gathered facts, because we're
+                                # returning the setup task to be executed
+                                host.set_gathered_facts(True)
                 else:
                     # This is the second trip through ITERATING_SETUP, so we clear
                     # the flag and move onto the next block in the list while setting
```

And for 2.0.1.0 I think that means:

```
diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py
index 1826432..1382133 100644
--- a/lib/ansible/executor/play_iterator.py
+++ b/lib/ansible/executor/play_iterator.py
@@ -255,9 +255,9 @@ class PlayIterator:
                        (gathering == 'explicit' and boolean(self._play.gather_facts)) or \
                        (gathering == 'smart' and implied and not host._gathered_facts):
                         # mark the host as having gathered facts
-                        host.set_gathered_facts(True)
                         setup_block = self._blocks[0]
                         if setup_block.has_tasks() and len(setup_block.block) > 0:
+                            host.set_gathered_facts(True)
                             task = setup_block.block[0]
                 else:
                     state.pending_setup = False
```

These changes seem to result in the appropriate behavior.


Resolving patch:
diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py
--- a/lib/ansible/executor/play_iterator.py
+++ b/lib/ansible/executor/play_iterator.py
@@ -295,10 +295,10 @@ def _get_next_task_from_state(self, state, host, peek):
                         setup_block = self._blocks[0]
                         if setup_block.has_tasks() and len(setup_block.block) > 0:
                             task = setup_block.block[0]
-                        if not peek:
-                            # mark the host as having gathered facts, because we're
-                            # returning the setup task to be executed
-                            host.set_gathered_facts(True)
+                            if not peek:
+                                # mark the host as having gathered facts, because we're
+                                # returning the setup task to be executed
+                                host.set_gathered_facts(True)
                 else:
                     # This is the second trip through ITERATING_SETUP, so we clear
                     # the flag and move onto the next block in the list while setting


Test patch: