# Buggy code
def load_list_of_tasks(
    ds,
    play,
    block=None,
    role=None,
    task_include=None,
    use_handlers=False,
    variable_manager=None,
    loader=None,
):
    """
    Given a list of task datastructures (parsed from YAML),
    return a list of Task() or TaskInclude() objects.
    """

    # we import here to prevent a circular dependency with imports
    from ansible.playbook.block import Block
    from ansible.playbook.handler import Handler
    from ansible.playbook.task import Task
    from ansible.playbook.task_include import TaskInclude
    from ansible.playbook.role_include import IncludeRole
    from ansible.playbook.handler_task_include import HandlerTaskInclude
    from ansible.template import Templar

    if not isinstance(ds, list):
        raise AnsibleAssertionError(
            "The ds (%s) should be a list but was a %s" % (ds, type(ds))
        )

    task_list = []
    for task_ds in ds:
        if not isinstance(task_ds, dict):
            AnsibleAssertionError(
                "The ds (%s) should be a dict but was a %s" % (ds, type(ds))
            )

        if "block" in task_ds:
            t = Block.load(
                task_ds,
                play=play,
                parent_block=block,
                role=role,
                task_include=task_include,
                use_handlers=use_handlers,
                variable_manager=variable_manager,
                loader=loader,
            )
            task_list.append(t)
        else:
            if (
                "include" in task_ds
                or "import_tasks" in task_ds
                or "include_tasks" in task_ds
            ):
                if use_handlers:
                    include_class = HandlerTaskInclude
                else:
                    include_class = TaskInclude

                t = include_class.load(
                    task_ds,
                    block=block,
                    role=role,
                    task_include=None,
                    variable_manager=variable_manager,
                    loader=loader,
                )

                all_vars = variable_manager.get_vars(play=play, task=t)
                templar = Templar(loader=loader, variables=all_vars)

                # 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 = True
                elif t.static is not None:
                    display.deprecated(
                        "The use of 'static' has been deprecated. "
                        "Use 'import_tasks' for static inclusion, or 'include_tasks' for dynamic inclusion"
                    )
                    is_static = t.static
                else:
                    is_static = (
                        C.DEFAULT_TASK_INCLUDES_STATIC
                        or (use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC)
                        or (
                            not templar._contains_vars(t.args["_raw_params"])
                            and t.all_parents_static()
                            and not t.loop
                        )
                    )

                if is_static:
                    if t.loop is not None:
                        if "import_tasks" in task_ds:
                            raise AnsibleParserError(
                                "You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.",
                                obj=task_ds,
                            )
                        else:
                            raise AnsibleParserError(
                                "You cannot use 'static' on an include with a loop",
                                obj=task_ds,
                            )

                    # we set a flag to indicate this include was static
                    t.statically_loaded = True

                    # handle relative includes by walking up the list of parent include
                    # tasks and checking the relative result to see if it exists
                    parent_include = block
                    cumulative_path = None

                    found = False
                    subdir = "tasks"
                    if use_handlers:
                        subdir = "handlers"
                    while parent_include is not None:
                        if not isinstance(parent_include, TaskInclude):
                            parent_include = parent_include._parent
                            continue
                        parent_include_dir = os.path.dirname(
                            templar.template(parent_include.args.get("_raw_params"))
                        )
                        if cumulative_path is None:
                            cumulative_path = parent_include_dir
                        elif not os.path.isabs(cumulative_path):
                            cumulative_path = os.path.join(
                                parent_include_dir, cumulative_path
                            )
                        include_target = templar.template(t.args["_raw_params"])
                        if t._role:
                            new_basedir = os.path.join(
                                t._role._role_path, subdir, cumulative_path
                            )
                            include_file = loader.path_dwim_relative(
                                new_basedir, subdir, include_target
                            )
                        else:
                            include_file = loader.path_dwim_relative(
                                loader.get_basedir(), cumulative_path, include_target
                            )

                        if os.path.exists(include_file):
                            found = True
                            break
                        else:
                            parent_include = parent_include._parent

                    if not found:
                        try:
                            include_target = templar.template(t.args["_raw_params"])
                        except AnsibleUndefinedVariable as e:
                            raise AnsibleParserError(
                                "Error when evaluating variable in include name: %s.\n\n"
                                "When using static includes, ensure that any variables used in their names are defined in vars/vars_files\n"
                                "or extra-vars passed in from the command line. Static includes cannot use variables from inventory\n"
                                "sources like group or host vars."
                                % t.args["_raw_params"],
                                obj=task_ds,
                                suppress_extended_error=True,
                                orig_exc=e,
                            )
                        if t._role:
                            include_file = loader.path_dwim_relative(
                                t._role._role_path, subdir, include_target
                            )
                        else:
                            include_file = loader.path_dwim(include_target)

                    try:
                        data = loader.load_from_file(include_file)
                        if data is None:
                            display.warning(
                                "file %s is empty and had no tasks to include"
                                % include_file
                            )
                            continue
                        elif not isinstance(data, list):
                            raise AnsibleParserError(
                                "included task files must contain a list of tasks",
                                obj=data,
                            )

                        # since we can't send callbacks here, we display a message directly in
                        # the same fashion used by the on_include callback. We also do it here,
                        # because the recursive nature of helper methods means we may be loading
                        # nested includes, and we want the include order printed correctly
                        display.vv("statically imported: %s" % include_file)
                    except AnsibleFileNotFound:
                        if (
                            t.static
                            or C.DEFAULT_TASK_INCLUDES_STATIC
                            or C.DEFAULT_HANDLER_INCLUDES_STATIC
                            and use_handlers
                        ):
                            raise
                        display.deprecated(
                            "Included file '%s' not found, however since this include is not "
                            "explicitly marked as 'static: yes', we will try and include it dynamically "
                            "later. In the future, this will be an error unless 'static: no' is used "
                            "on the include task. If you do not want missing includes to be considered "
                            "dynamic, use 'static: yes' on the include or set the global ansible.cfg "
                            "options to make all includes static for tasks and/or handlers"
                            % include_file,
                            version="2.7",
                        )
                        task_list.append(t)
                        continue

                    ti_copy = t.copy(exclude_parent=True)
                    ti_copy._parent = block
                    included_blocks = load_list_of_blocks(
                        data,
                        play=play,
                        parent_block=None,
                        task_include=ti_copy,
                        role=role,
                        use_handlers=use_handlers,
                        loader=loader,
                        variable_manager=variable_manager,
                    )

                    # FIXME: remove once 'include' is removed
                    # pop tags out of the include args, if they were specified there, and assign
                    # them to the include. If the include already had tags specified, we raise an
                    # error so that users know not to specify them both ways
                    tags = ti_copy.vars.pop("tags", [])
                    if isinstance(tags, string_types):
                        tags = tags.split(",")

                    if len(tags) > 0:
                        if "include_tasks" in task_ds or "import_tasks" in task_ds:
                            raise AnsibleParserError(
                                'You cannot specify "tags" inline to the task, it is a task keyword'
                            )
                        if len(ti_copy.tags) > 0:
                            raise AnsibleParserError(
                                "Include tasks should not specify tags in more than one way (both via args and directly on the task). "
                                "Mixing styles in which tags are specified is prohibited for whole import hierarchy, not only for single import statement",
                                obj=task_ds,
                                suppress_extended_error=True,
                            )
                        display.deprecated(
                            "You should not specify tags in the include parameters. All tags should be specified using the task-level option",
                            version="2.7",
                        )
                    else:
                        tags = ti_copy.tags[:]

                    # now we extend the tags on each of the included blocks
                    for b in included_blocks:
                        b.tags = list(set(b.tags).union(tags))
                    # END FIXME

                    # FIXME: handlers shouldn't need this special handling, but do
                    #        right now because they don't iterate blocks correctly
                    if use_handlers:
                        for b in included_blocks:
                            task_list.extend(b.block)
                    else:
                        task_list.extend(included_blocks)
                else:
                    t.is_static = False
                    task_list.append(t)

            elif "include_role" in task_ds or "import_role" in task_ds:
                ir = IncludeRole.load(
                    task_ds,
                    block=block,
                    role=role,
                    task_include=None,
                    variable_manager=variable_manager,
                    loader=loader,
                )

                #   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

                elif ir.static is not None:
                    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

                if is_static:
                    if ir.loop is not None:
                        if "import_role" in task_ds:
                            raise AnsibleParserError(
                                "You cannot use loops on 'import_role' statements. You should use 'include_role' instead.",
                                obj=task_ds,
                            )
                        else:
                            raise AnsibleParserError(
                                "You cannot use 'static' on an include_role with a loop",
                                obj=task_ds,
                            )

                    # 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
                    )
                    task_list.extend(blocks)
                else:
                    # passes task object itself for latter generation of list
                    task_list.append(ir)
            else:
                if use_handlers:
                    t = Handler.load(
                        task_ds,
                        block=block,
                        role=role,
                        task_include=task_include,
                        variable_manager=variable_manager,
                        loader=loader,
                    )
                else:
                    t = Task.load(
                        task_ds,
                        block=block,
                        role=role,
                        task_include=task_include,
                        variable_manager=variable_manager,
                        loader=loader,
                    )

                task_list.append(t)

    return task_list


# Traceback (AttributeError)
$ ansible-playbook -i "localhost," -c local playbook.yml -vvv
ansible-playbook 2.4.0 (devel b35f39479b) last updated 2017/08/30 15:12:04 (GMT +200)
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/home/user/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /home/user/Repos/ansible/lib/ansible
executable location = /home/user/Repos/ansible/bin/ansible-playbook
python version = 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4]
Using /etc/ansible/ansible.cfg as config file
Parsed localhost, inventory source with host_list plugin

PLAYBOOK: playbook.yml ************************************************************************
1 plays in playbook.yml

PLAY [Local action include role] **************************************************************

TASK [Gathering Facts] ************************************************************************
Using module file /home/user/Repos/ansible/lib/ansible/modules/system/setup.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: user
<localhost> EXEC /bin/sh -c 'echo ~ &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608 `" &amp;&amp; echo ansible-tmp-1504099569.5-119095676611608="` echo /home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608 `" ) &amp;&amp; sleep 0'
<localhost> PUT /tmp/tmpnGFBA0 TO /home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608/setup.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608/ /home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608/setup.py &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608/setup.py; rm -rf "/home/user/.ansible/tmp/ansible-tmp-1504099569.5-119095676611608/" > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
ok: [localhost]
META: ran handlers

TASK [Trigger bug] ****************************************************************************
task path: /home/user/Test/ansible-block/playbook.yml:6
[WARNING]: Failure using method (v2_runner_on_ok) in callback plugin
(<ansible.plugins.callback.default.CallbackModule object at 0x7fbe3cf06ad0>): TASK: Trigger
bug is not JSON serializable

ERROR! Unexpected Exception, this is probably a bug: 'Task' object has no attribute 'get_block_list'
the full traceback was:

Traceback (most recent call last):
File "/home/user/Repos/ansible/bin/ansible-playbook", line 106, in <module>
exit_code = cli.run()
File "/home/user/Repos/ansible/lib/ansible/cli/playbook.py", line 130, in run
results = pbex.run()
File "/home/user/Repos/ansible/lib/ansible/executor/playbook_executor.py", line 154, in run
result = self._tqm.run(play=play)
File "/home/user/Repos/ansible/lib/ansible/executor/task_queue_manager.py", line 289, in run
play_return = strategy.run(iterator, play_context)
File "/home/user/Repos/ansible/lib/ansible/plugins/strategy/linear.py", line 318, in run
blocks, handler_blocks = new_ir.get_block_list(play=iterator._play, variable_manager=self._variable_manager, loader=self._loader)
AttributeError: 'Task' object has no attribute 'get_block_list'

# Fixed code
def load_list_of_tasks(
    ds,
    play,
    block=None,
    role=None,
    task_include=None,
    use_handlers=False,
    variable_manager=None,
    loader=None,
):
    """
    Given a list of task datastructures (parsed from YAML),
    return a list of Task() or TaskInclude() objects.
    """

    # we import here to prevent a circular dependency with imports
    from ansible.playbook.block import Block
    from ansible.playbook.handler import Handler
    from ansible.playbook.task import Task
    from ansible.playbook.task_include import TaskInclude
    from ansible.playbook.role_include import IncludeRole
    from ansible.playbook.handler_task_include import HandlerTaskInclude
    from ansible.template import Templar

    if not isinstance(ds, list):
        raise AnsibleAssertionError(
            "The ds (%s) should be a list but was a %s" % (ds, type(ds))
        )

    task_list = []
    for task_ds in ds:
        if not isinstance(task_ds, dict):
            AnsibleAssertionError(
                "The ds (%s) should be a dict but was a %s" % (ds, type(ds))
            )

        if "block" in task_ds:
            t = Block.load(
                task_ds,
                play=play,
                parent_block=block,
                role=role,
                task_include=task_include,
                use_handlers=use_handlers,
                variable_manager=variable_manager,
                loader=loader,
            )
            task_list.append(t)
        else:
            args_parser = ModuleArgsParser(task_ds)
            try:
                (action, args, delegate_to) = args_parser.parse()
            except AnsibleParserError as e:
                # if the raises exception was created with obj=ds args, then it includes the detail
                # so we dont need to add it so we can just re raise.
                if e._obj:
                    raise
                # But if it wasn't, we can add the yaml object now to get more detail
                raise AnsibleParserError(to_native(e), obj=task_ds, orig_exc=e)

            if action in ("include", "import_tasks", "include_tasks"):
                if use_handlers:
                    include_class = HandlerTaskInclude
                else:
                    include_class = TaskInclude

                t = include_class.load(
                    task_ds,
                    block=block,
                    role=role,
                    task_include=None,
                    variable_manager=variable_manager,
                    loader=loader,
                )

                all_vars = variable_manager.get_vars(play=play, task=t)
                templar = Templar(loader=loader, variables=all_vars)

                # 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 action == "include_tasks":
                    is_static = False
                elif action == "import_tasks":
                    is_static = True
                elif t.static is not None:
                    display.deprecated(
                        "The use of 'static' has been deprecated. "
                        "Use 'import_tasks' for static inclusion, or 'include_tasks' for dynamic inclusion"
                    )
                    is_static = t.static
                else:
                    is_static = (
                        C.DEFAULT_TASK_INCLUDES_STATIC
                        or (use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC)
                        or (
                            not templar._contains_vars(t.args["_raw_params"])
                            and t.all_parents_static()
                            and not t.loop
                        )
                    )

                if is_static:
                    if t.loop is not None:
                        if action == "import_tasks":
                            raise AnsibleParserError(
                                "You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.",
                                obj=task_ds,
                            )
                        else:
                            raise AnsibleParserError(
                                "You cannot use 'static' on an include with a loop",
                                obj=task_ds,
                            )

                    # we set a flag to indicate this include was static
                    t.statically_loaded = True

                    # handle relative includes by walking up the list of parent include
                    # tasks and checking the relative result to see if it exists
                    parent_include = block
                    cumulative_path = None

                    found = False
                    subdir = "tasks"
                    if use_handlers:
                        subdir = "handlers"
                    while parent_include is not None:
                        if not isinstance(parent_include, TaskInclude):
                            parent_include = parent_include._parent
                            continue
                        parent_include_dir = os.path.dirname(
                            templar.template(parent_include.args.get("_raw_params"))
                        )
                        if cumulative_path is None:
                            cumulative_path = parent_include_dir
                        elif not os.path.isabs(cumulative_path):
                            cumulative_path = os.path.join(
                                parent_include_dir, cumulative_path
                            )
                        include_target = templar.template(t.args["_raw_params"])
                        if t._role:
                            new_basedir = os.path.join(
                                t._role._role_path, subdir, cumulative_path
                            )
                            include_file = loader.path_dwim_relative(
                                new_basedir, subdir, include_target
                            )
                        else:
                            include_file = loader.path_dwim_relative(
                                loader.get_basedir(), cumulative_path, include_target
                            )

                        if os.path.exists(include_file):
                            found = True
                            break
                        else:
                            parent_include = parent_include._parent

                    if not found:
                        try:
                            include_target = templar.template(t.args["_raw_params"])
                        except AnsibleUndefinedVariable as e:
                            raise AnsibleParserError(
                                "Error when evaluating variable in include name: %s.\n\n"
                                "When using static includes, ensure that any variables used in their names are defined in vars/vars_files\n"
                                "or extra-vars passed in from the command line. Static includes cannot use variables from inventory\n"
                                "sources like group or host vars."
                                % t.args["_raw_params"],
                                obj=task_ds,
                                suppress_extended_error=True,
                                orig_exc=e,
                            )
                        if t._role:
                            include_file = loader.path_dwim_relative(
                                t._role._role_path, subdir, include_target
                            )
                        else:
                            include_file = loader.path_dwim(include_target)

                    try:
                        data = loader.load_from_file(include_file)
                        if data is None:
                            display.warning(
                                "file %s is empty and had no tasks to include"
                                % include_file
                            )
                            continue
                        elif not isinstance(data, list):
                            raise AnsibleParserError(
                                "included task files must contain a list of tasks",
                                obj=data,
                            )

                        # since we can't send callbacks here, we display a message directly in
                        # the same fashion used by the on_include callback. We also do it here,
                        # because the recursive nature of helper methods means we may be loading
                        # nested includes, and we want the include order printed correctly
                        display.vv("statically imported: %s" % include_file)
                    except AnsibleFileNotFound:
                        if (
                            t.static
                            or C.DEFAULT_TASK_INCLUDES_STATIC
                            or C.DEFAULT_HANDLER_INCLUDES_STATIC
                            and use_handlers
                        ):
                            raise
                        display.deprecated(
                            "Included file '%s' not found, however since this include is not "
                            "explicitly marked as 'static: yes', we will try and include it dynamically "
                            "later. In the future, this will be an error unless 'static: no' is used "
                            "on the include task. If you do not want missing includes to be considered "
                            "dynamic, use 'static: yes' on the include or set the global ansible.cfg "
                            "options to make all includes static for tasks and/or handlers"
                            % include_file,
                            version="2.7",
                        )
                        task_list.append(t)
                        continue

                    ti_copy = t.copy(exclude_parent=True)
                    ti_copy._parent = block
                    included_blocks = load_list_of_blocks(
                        data,
                        play=play,
                        parent_block=None,
                        task_include=ti_copy,
                        role=role,
                        use_handlers=use_handlers,
                        loader=loader,
                        variable_manager=variable_manager,
                    )

                    # FIXME: remove once 'include' is removed
                    # pop tags out of the include args, if they were specified there, and assign
                    # them to the include. If the include already had tags specified, we raise an
                    # error so that users know not to specify them both ways
                    tags = ti_copy.vars.pop("tags", [])
                    if isinstance(tags, string_types):
                        tags = tags.split(",")

                    if len(tags) > 0:
                        if action in ("include_tasks", "import_tasks"):
                            raise AnsibleParserError(
                                'You cannot specify "tags" inline to the task, it is a task keyword'
                            )
                        if len(ti_copy.tags) > 0:
                            raise AnsibleParserError(
                                "Include tasks should not specify tags in more than one way (both via args and directly on the task). "
                                "Mixing styles in which tags are specified is prohibited for whole import hierarchy, not only for single import statement",
                                obj=task_ds,
                                suppress_extended_error=True,
                            )
                        display.deprecated(
                            "You should not specify tags in the include parameters. All tags should be specified using the task-level option",
                            version="2.7",
                        )
                    else:
                        tags = ti_copy.tags[:]

                    # now we extend the tags on each of the included blocks
                    for b in included_blocks:
                        b.tags = list(set(b.tags).union(tags))
                    # END FIXME

                    # FIXME: handlers shouldn't need this special handling, but do
                    #        right now because they don't iterate blocks correctly
                    if use_handlers:
                        for b in included_blocks:
                            task_list.extend(b.block)
                    else:
                        task_list.extend(included_blocks)
                else:
                    t.is_static = False
                    task_list.append(t)

            elif action in ("include_role", "import_role"):
                ir = IncludeRole.load(
                    task_ds,
                    block=block,
                    role=role,
                    task_include=None,
                    variable_manager=variable_manager,
                    loader=loader,
                )

                #   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 action == "import_role":
                    is_static = True

                elif ir.static is not None:
                    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

                if is_static:
                    if ir.loop is not None:
                        if action == "import_role":
                            raise AnsibleParserError(
                                "You cannot use loops on 'import_role' statements. You should use 'include_role' instead.",
                                obj=task_ds,
                            )
                        else:
                            raise AnsibleParserError(
                                "You cannot use 'static' on an include_role with a loop",
                                obj=task_ds,
                            )

                    # 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
                    )
                    task_list.extend(blocks)
                else:
                    # passes task object itself for latter generation of list
                    task_list.append(ir)
            else:
                if use_handlers:
                    t = Handler.load(
                        task_ds,
                        block=block,
                        role=role,
                        task_include=task_include,
                        variable_manager=variable_manager,
                        loader=loader,
                    )
                else:
                    t = Task.load(
                        task_ds,
                        block=block,
                        role=role,
                        task_include=task_include,
                        variable_manager=variable_manager,
                        loader=loader,
                    )

                task_list.append(t)

    return task_list