Repository: ansible/ansible

Issue:
vars used in include_role or import_role are evaluated too early
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
include_role
import_role

##### ANSIBLE VERSION
```
ansible-playbook 2.4.0.0
  config file = 
  configured module search path = [u'/Users/lynchg/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/lynchg/.virtualenvs/a2x/lib/python2.7/site-packages/ansible
  executable location = /Users/lynchg/.virtualenvs/a2x/bin/ansible-playbook
  python version = 2.7.13 (default, Dec 18 2016, 07:03:34) [GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)]
```

##### CONFIGURATION
NA

##### OS / ENVIRONMENT
Mac OSX

##### SUMMARY
When using a variable as the target for an include_role or import_role, it is evaluated at the beginning of an ansible-playbook run, before it may exist.

##### STEPS TO REPRODUCE
https://gist.github.com/halberom/2c9aa046f63b096f78b26401a11e78d4

Create a playbook
```
# play.yml
---
- hosts: localhost
  gather_facts: False
  connection: local
  roles:
    - { role: test1, rolename: test2 }
```
Create a role
```
# roles/test1/tasks/main.yml
- debug: var=rolename

- include_role:
    name: "{{ rolename }}"
```
Create a second role
```
# roles/test2/tasks/main.yml
- debug: msg="in {{ role_path }}"
```

##### EXPECTED RESULTS
In the gist - uncommenting the import_role or include_role statements in the gist should work without bugs.

In the example above - you should get a "in /path/to/role" output from the debug in the `test2` role.

##### ACTUAL RESULTS
No tasks are processed, and an error is output

```
# ERROR! 'rolename' is undefined
```
where `rolename` is the variable being used in the gist.

It works if you move the `rolename: test2` to the vars section of the play.  See also the alternative playbook in the gist.


Discussion:
Note this was first reported by stanislava in irc, with a link to https://gist.github.com/XooR/f5f15b0610ba6c15c4820e89a2b92ce0
Some more info for this:
- Works correctly if the included role `test1` doesn't have an `include_role` task
- Works correctly if the `rolename` var is defined in the play `vars:` section
Started some intg tests for this at https://github.com/ansible/ansible/compare/devel...alikins:include_role_intg_tests

Do the tests at https://github.com/ansible/ansible/compare/devel...alikins:include_role_intg_tests?expand=1#diff-201e87e7cc1d4faad12a30a3a540c8c9 and https://github.com/ansible/ansible/compare/devel...alikins:include_role_intg_tests?expand=1#diff-9223a0c4b39f8ab67818bac876e944c7 look like a good reproducer?

running the test_incldue_role_no_vars.yml playbook against devel(cc288aba47) I get:

```
+ ansible-playbook -i ../../inventory -vvv test_include_role_no_vars.yml
ansible-playbook 2.5.0
  config file = None
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /root/ansible/lib/ansible
  executable location = /root/ansible/bin/ansible-playbook
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
No config file found; using defaults
Parsed /root/ansible/test/integration/inventory inventory source with ini plugin
ERROR! 'rolename' is undefined
```

@alikins Yep that's what I was doing too, it does look good to me!

Resolving patch:
diff --git a/lib/ansible/playbook/helpers.py b/lib/ansible/playbook/helpers.py
--- a/lib/ansible/playbook/helpers.py
+++ b/lib/ansible/playbook/helpers.py
@@ -133,9 +133,8 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                 # check to see if this include is dynamic or static:
                 # 1. the user has set the 'static' option to false or true
                 # 2. one of the appropriate config options was set
-                if 'include_tasks' in task_ds:
-                    is_static = False
-                elif 'import_tasks' in task_ds:
+                is_static = False
+                if 'import_tasks' in task_ds:
                     is_static = True
                 elif t.static is not None:
                     display.deprecated("The use of 'static' has been deprecated. "
@@ -294,6 +293,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
 
                 #   1. the user has set the 'static' option to false or true
                 #   2. one of the appropriate config options was set
+                is_static = False
                 if 'import_role' in task_ds:
                     is_static = True
 
@@ -301,23 +301,6 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                     display.deprecated("The use of 'static' for 'include_role' has been deprecated. "
                                        "Use 'import_role' for static inclusion, or 'include_role' for dynamic inclusion")
                     is_static = ir.static
-                else:
-                    display.debug('Determine if include_role is static')
-                    # Check to see if this include is dynamic or static:
-                    all_vars = variable_manager.get_vars(play=play, task=ir)
-                    templar = Templar(loader=loader, variables=all_vars)
-                    needs_templating = False
-                    for param in ir.args:
-                        if templar._contains_vars(ir.args[param]):
-                            if not templar.is_template(ir.args[param]):
-                                needs_templating = True
-                                break
-                    is_static = (
-                        C.DEFAULT_TASK_INCLUDES_STATIC or
-                        (use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or
-                        (not needs_templating and ir.all_parents_static() and not ir.loop)
-                    )
-                    display.debug('Determined that if include_role static is %s' % str(is_static))
 
                 if is_static:
                     if ir.loop is not None:
@@ -329,6 +312,12 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                     # we set a flag to indicate this include was static
                     ir.statically_loaded = True
 
+                    # template the role name now, if needed
+                    all_vars = variable_manager.get_vars(play=play, task=ir)
+                    templar = Templar(loader=loader, variables=all_vars)
+                    if templar._contains_vars(ir._role_name):
+                        ir._role_name = templar.template(ir._role_name)
+
                     # uses compiled list from object
                     blocks, _ = ir.get_block_list(variable_manager=variable_manager, loader=loader)
                     t = task_list.extend(blocks)


Test patch: