Esempio n. 1
0
    def _include_tasks_in_blocks(self, current_play: Play, graph: CustomDigraph, parent_node_name: str,
                                 parent_node_id: str, block: Union[Block, TaskInclude], color: str,
                                 current_counter: int, play_vars: Dict = None, node_name_prefix: str = "") -> int:
        """
        Recursively read all the tasks of the block and add it to the graph
        FIXME: This function needs some refactoring. Thinking of a BlockGrapher to handle this
        :param current_play:
        :param graph:
        :param parent_node_name:
        :param parent_node_id:
        :param block:
        :param color:
        :param current_counter:
        :param play_vars:
        :param node_name_prefix:
        :return:
        """

        # loop through the tasks
        for counter, task_or_block in enumerate(block.block, 1):
            if isinstance(task_or_block, Block):
                current_counter = self._include_tasks_in_blocks(current_play=current_play, graph=graph,
                                                                parent_node_name=parent_node_name,
                                                                parent_node_id=parent_node_id, block=task_or_block,
                                                                color=color, current_counter=current_counter,
                                                                play_vars=play_vars, node_name_prefix=node_name_prefix)
            elif isinstance(task_or_block, TaskInclude):  # include, include_tasks, include_role are dynamic
                # So we need to process them explicitly because Ansible does it during the execution of the playbook

                task_vars = self.variable_manager.get_vars(play=current_play, task=task_or_block)

                if isinstance(task_or_block, IncludeRole):
                    # Here we have an 'include_role'. The class IncludeRole is a subclass of TaskInclude.
                    # We do this because the management of an 'include_role' is different.
                    # See :func:`~ansible.playbook.included_file.IncludedFile.process_include_results` from line 155

                    self.display.v(f"An 'include_role' found. Including tasks from '{task_or_block.args['name']}'")
                    my_blocks, _ = task_or_block.get_block_list(play=current_play, loader=self.data_loader,
                                                                variable_manager=self.variable_manager)
                else:
                    self.display.v(f"An 'include_tasks' found. Including tasks from '{task_or_block.get_name()}'")

                    templar = Templar(loader=self.data_loader, variables=task_vars)
                    try:
                        include_file = handle_include_path(original_task=task_or_block, loader=self.data_loader,
                                                           templar=templar)
                    except AnsibleUndefinedVariable as e:
                        # TODO: mark this task with some special shape or color
                        self.display.warning(
                            f"Unable to translate the include task '{task_or_block.get_name()}' due to an undefined variable: {str(e)}. "
                            "Some variables are available only during the execution of the playbook.")
                        current_counter += 1
                        self._graph_task(task=task_or_block, loop_counter=current_counter, task_vars=task_vars,
                                         graph=graph, node_name_prefix=node_name_prefix, color=color,
                                         parent_node_id=parent_node_id, parent_node_name=parent_node_name)
                        continue

                    data = self.data_loader.load_from_file(include_file)
                    if data is None:
                        self.display.warning(f"The file '{include_file}' is empty and has no tasks to include")
                        continue
                    elif not isinstance(data, list):
                        raise AnsibleParserError("Included task files must contain a list of tasks", obj=data)

                    # get the blocks from the include_tasks
                    my_blocks = load_list_of_blocks(data, play=current_play, variable_manager=self.variable_manager,
                                                    role=task_or_block._role, loader=self.data_loader,
                                                    parent_block=task_or_block)

                for b in my_blocks:  # loop through the blocks inside the included tasks or role
                    current_counter = self._include_tasks_in_blocks(current_play=current_play, graph=graph,
                                                                    parent_node_name=parent_node_name,
                                                                    parent_node_id=parent_node_id, block=b, color=color,
                                                                    current_counter=current_counter,
                                                                    play_vars=task_vars,
                                                                    node_name_prefix=node_name_prefix)
            else:
                # check if this task comes from a role, and we don't want to include tasks of the role
                if has_role_parent(task_or_block) and not self.include_role_tasks:
                    # skip role's task
                    self.display.vv(
                        f"The task '{task_or_block.get_name()}' has a role as parent and include_role_tasks is false. "
                        "It will be skipped.")
                    # skipping
                    continue

                task_included = self._graph_task(task=task_or_block, loop_counter=current_counter + 1,
                                                 task_vars=play_vars, graph=graph, node_name_prefix=node_name_prefix,
                                                 color=color, parent_node_id=parent_node_id,
                                                 parent_node_name=parent_node_name)
                if task_included:
                    # only increment the counter if task has been successfully included.
                    current_counter += 1

        return current_counter
Esempio n. 2
0
    def _include_tasks_in_blocks(
        self,
        current_play: Play,
        parent_nodes: List[CompositeNode],
        block: Union[Block, TaskInclude],
        node_type: str,
        play_vars: Dict,
    ):
        """
        Recursively read all the tasks of the block and add it to the graph
        :param parent_nodes: This a list of parent nodes. Each time, we see an include_role, the corresponding node is
        added to this list
        :param current_play:
        :param block:
        :param play_vars:
        :param node_type:
        :return:
        """

        if not block._implicit and block._role is None:
            # Here we have an explicit block. Ansible internally converts all normal tasks to Block
            block_node = BlockNode(
                str(block.name),
                when=convert_when_to_str(block.when),
                raw_object=block,
                parent=parent_nodes[-1],
            )
            parent_nodes[-1].add_node(f"{node_type}s", block_node)
            parent_nodes.append(block_node)

        # loop through the tasks
        for task_or_block in block.block:

            if hasattr(task_or_block, "loop") and task_or_block.loop:
                display.warning(
                    "Looping on tasks or roles are not supported for the moment. "
                    f"Only the task having the loop argument will be added to the graph."
                )

            if isinstance(task_or_block, Block):
                self._include_tasks_in_blocks(
                    current_play=current_play,
                    parent_nodes=parent_nodes,
                    block=task_or_block,
                    node_type=node_type,
                    play_vars=play_vars,
                )
            elif isinstance(
                    task_or_block, TaskInclude
            ):  # include, include_tasks, include_role are dynamic
                # So we need to process them explicitly because Ansible does it during the execution of the playbook

                task_vars = self.variable_manager.get_vars(play=current_play,
                                                           task=task_or_block)

                if isinstance(task_or_block, IncludeRole):
                    # Here we have an 'include_role'. The class IncludeRole is a subclass of TaskInclude.
                    # We do this because the management of an 'include_role' is different.
                    # See :func:`~ansible.playbook.included_file.IncludedFile.process_include_results` from line 155
                    display.v(
                        f"An 'include_role' found: '{task_or_block.get_name()}'"
                    )

                    # Here we are using the role name instead of the task name to keep the same behavior  as a
                    #  traditional role
                    if self.group_roles_by_name:
                        # If we are grouping roles, we use the hash of role name as the node id
                        role_node_id = "role_" + hash_value(
                            task_or_block._role_name)
                    else:
                        # Otherwise, a random id is used
                        role_node_id = generate_id("role_")
                    role_node = RoleNode(
                        task_or_block._role_name,
                        node_id=role_node_id,
                        when=convert_when_to_str(task_or_block.when),
                        raw_object=task_or_block,
                        parent=parent_nodes[-1],
                        include_role=True,
                    )
                    parent_nodes[-1].add_node(
                        f"{node_type}s",
                        role_node,
                    )

                    if task_or_block.loop:  # Looping on include_role is not supported
                        continue  # Go the next task
                    else:
                        if self.include_role_tasks:
                            # If we have an include_role, and we want to include its tasks, the parent node now becomes
                            # the role.
                            parent_nodes.append(role_node)

                        block_list, _ = task_or_block.get_block_list(
                            play=current_play,
                            loader=self.data_loader,
                            variable_manager=self.variable_manager,
                        )
                else:
                    display.v(
                        f"An 'include_tasks' found. Including tasks from '{task_or_block.get_name()}'"
                    )

                    templar = Templar(loader=self.data_loader,
                                      variables=task_vars)
                    try:
                        included_file_path = handle_include_path(
                            original_task=task_or_block,
                            loader=self.data_loader,
                            templar=templar,
                        )
                    except AnsibleUndefinedVariable as e:
                        # TODO: mark this task with some special shape or color
                        display.warning(
                            f"Unable to translate the include task '{task_or_block.get_name()}' due to an undefined variable: {str(e)}. "
                            "Some variables are available only during the execution of the playbook."
                        )
                        self._add_task(
                            task=task_or_block,
                            task_vars=task_vars,
                            node_type=node_type,
                            parent_node=parent_nodes[-1],
                        )
                        continue

                    data = self.data_loader.load_from_file(included_file_path)
                    if data is None:
                        display.warning(
                            f"The file '{included_file_path}' is empty and has no tasks to include"
                        )
                        continue
                    elif not isinstance(data, list):
                        raise AnsibleParserError(
                            "Included task files must contain a list of tasks",
                            obj=data)

                    # get the blocks from the include_tasks
                    block_list = load_list_of_blocks(
                        data,
                        play=current_play,
                        variable_manager=self.variable_manager,
                        role=task_or_block._role,
                        loader=self.data_loader,
                        parent_block=task_or_block,
                    )

                for (b) in (
                        block_list
                ):  # loop through the blocks inside the included tasks or role
                    self._include_tasks_in_blocks(
                        current_play=current_play,
                        parent_nodes=parent_nodes,
                        block=b,
                        play_vars=task_vars,
                        node_type=node_type,
                    )
                if (self.include_role_tasks
                        and isinstance(task_or_block, IncludeRole)
                        and len(parent_nodes) > 1):
                    # We remove the parent node we have added if we included some tasks from a role
                    parent_nodes.pop()
            else:  # It's here that we add the task in the graph
                if (len(parent_nodes) > 1  # 1
                        and not has_role_parent(task_or_block)  # 2
                        and parent_nodes[-1].raw_object !=
                        task_or_block._parent  # 3
                    ):
                    # We remove a parent node :
                    # 1. When have at least two parents. Every node (except the playbook) should have a parent node
                    #   AND
                    # 2. The current node doesn't have a role as parent
                    #   AND
                    # 3. The last parent node is different from the current node parent. This means that we are
                    #   done with the child nodes of this parent node
                    parent_nodes.pop()

                # check if this task comes from a role, and we don't want to include tasks of the role
                if has_role_parent(
                        task_or_block) and not self.include_role_tasks:
                    # skip role's task
                    display.vv(
                        f"The task '{task_or_block.get_name()}' has a role as parent and include_role_tasks is false. "
                        "It will be skipped.")
                    # skipping
                    continue

                self._add_task(
                    task=task_or_block,
                    task_vars=play_vars,
                    node_type=node_type,
                    parent_node=parent_nodes[-1],
                )