Example #1
0
    def _add_task(self, task: Task, task_vars: Dict, node_type: str,
                  parent_node: CompositeNode) -> bool:
        """
        Include the task in the graph.
        :return: True if the task has been included, false otherwise
        """

        # Ansible-core 2.11 added an implicit meta tasks at the end of the role. So wee skip it here.
        if task.action == "meta" and task.implicit:
            return False

        if not task.evaluate_tags(only_tags=self.tags,
                                  skip_tags=self.skip_tags,
                                  all_vars=task_vars):
            display.vv(
                f"The task '{task.get_name()}' is skipped due to the tags.")
            return False

        display.vv(f"Adding {node_type} '{task.get_name()}' to the graph")

        task_name = clean_name(self.template(task.get_name(), task_vars))
        parent_node.add_node(
            target_composition=f"{node_type}s",
            node=TaskNode(
                task_name,
                generate_id(f"{node_type}_"),
                when=convert_when_to_str(task.when),
                raw_object=task,
                parent=parent_node,
            ),
        )

        return True
Example #2
0
    def _graph_task(self, task: Task, loop_counter: int, task_vars: Dict, graph: CustomDigraph, node_name_prefix: str,
                    color: str, parent_node_id: str, parent_node_name: str) -> bool:
        """
        Include the task in the graph.
        :return: True if the task has been included, false otherwise
        """

        self.display.vv(f"Adding {node_name_prefix.strip()}: '{task.get_name()}' to the graph")

        if not task.evaluate_tags(only_tags=self.tags, skip_tags=self.skip_tags, all_vars=task_vars):
            self.display.vv(f"The task '{task.get_name()}' is skipped due to the tags.")
            return False

        task_edge_label = str(loop_counter)
        if len(task.when) > 0:
            when = "".join(map(str, task.when))
            task_edge_label += "  [when: " + when + "]"

        task_name = clean_name(node_name_prefix + self.template(task.get_name(), task_vars))
        # get prefix id from node_name
        id_prefix = node_name_prefix.replace("[", "").replace("]", "").replace(" ", "_")
        task_id = id_prefix + generate_id()
        edge_id = "edge_" + generate_id()

        graph.node(task_id, label=task_name, shape="octagon", id=task_id, tooltip=task_name)
        graph.edge(parent_node_name, task_id, label=task_edge_label, color=color, fontcolor=color, style="bold",
                   id=edge_id)
        self.graph_representation.add_link(parent_node_id, edge_id)
        self.graph_representation.add_link(edge_id, task_id)

        return True
Example #3
0
    def _include_task(self, task_or_block, loop_counter, play_vars, graph, node_name_prefix, color, parent_node_id,
                      parent_node_name):
        """
        Include the task in the graph.
        :return: True if the task has been included, false otherwise
        :rtype: bool
        """

        self.display.vv("Adding the task '{}' to the graph".format(task_or_block.get_name()))

        if not task_or_block.evaluate_tags(only_tags=self.options.tags, skip_tags=self.options.skip_tags,
                                           all_vars=play_vars):
            self.display.vv("The task '{}' is skipped due to the tags.".format(task_or_block.get_name()))
            return False

        task_edge_label = str(loop_counter)
        if len(task_or_block.when) > 0:
            when = "".join(map(str, task_or_block.when))
            task_edge_label += "  [when: " + when + "]"

        task_name = clean_name(node_name_prefix + self.template(task_or_block.get_name(), play_vars))
        # get prefix id from node_name
        id_prefix = node_name_prefix.replace("[", "").replace("]", "").replace(" ", "_")
        task_id = id_prefix + str(uuid.uuid4())
        edge_id = "edge_" + str(uuid.uuid4())

        graph.node(task_id, label=task_name, shape="octagon", id=task_id)
        graph.edge(parent_node_name, task_id, label=task_edge_label, color=color, fontcolor=color, style="bold",
                   id=edge_id)
        self.graph_representation.add_link(parent_node_id, edge_id)
        self.graph_representation.add_link(edge_id, task_id)

        return True
    def _include_task(self, task_or_block, loop_counter, play_vars, graph,
                      node_name_prefix, color, parent_node_id,
                      parent_node_name):
        """
        Include the task in the graph
        :return:
        :rtype:
        """
        self.display.vv("Adding the task '{}' to the graph".format(
            task_or_block.get_name()))
        # check if the task should be included
        tagged = ''
        if not task_or_block.evaluate_tags(only_tags=self.options.tags,
                                           skip_tags=self.options.skip_tags,
                                           all_vars=play_vars):
            self.display.vv(
                "The task '{}' should not be executed. It will be marked as NOT_TAGGED"
                .format(task_or_block.get_name()))
            tagged = NOT_TAGGED

        task_edge_label = str(loop_counter)
        if len(task_or_block.when) > 0:
            when = "".join(map(str, task_or_block.when))
            task_edge_label += "  [when: " + when + "]"

        task_name = clean_name(
            node_name_prefix +
            self.template(task_or_block.get_name(), play_vars))
        # get prefix id from node_name
        id_prefix = node_name_prefix.replace("[",
                                             "").replace("]",
                                                         "").replace(" ", "_")
        task_id = id_prefix + str(uuid.uuid4()) + tagged
        edge_id = "edge_" + str(uuid.uuid4()) + tagged

        graph.node(task_name, shape="octagon", id=task_id)
        graph.edge(parent_node_name,
                   task_name,
                   label=task_edge_label,
                   color=color,
                   fontcolor=color,
                   style="bold",
                   id=edge_id)
        self.graph_representation.add_link(parent_node_id, edge_id)
        self.graph_representation.add_link(edge_id, task_id)
Example #5
0
    def _include_tasks_in_blocks(self,
                                 current_play,
                                 graph,
                                 parent_node_name,
                                 parent_node_id,
                                 block,
                                 color,
                                 current_counter,
                                 play_vars=None,
                                 node_name_prefix='',
                                 tags=None,
                                 skip_tags=None):
        """
        Recursively read all the tasks of the block and add it to the graph
        :param current_play:
        :type current_play: ansible.playbook.play.Play
        :param graph:
        :type graph:
        :param parent_node_name:
        :type parent_node_name: str
        :param parent_node_id:
        :type parent_node_id: str
        :param block:
        :type block: Union[Block,TaskInclude]
        :param color:
        :type color: str
        :param current_counter:
        :type current_counter: int
        :param play_vars:
        :type play_vars: dict
        :param node_name_prefix:
        :type node_name_prefix: str
        :param tags:
        :type tags: list
        :param skip_tags:
        :type skip_tags: list
        :return:
        :rtype:
        """
        if tags is None:
            tags = ['all']

        if skip_tags is None:
            skip_tags = []

        # get prefix id from node_name
        id_prefix = node_name_prefix.replace("[",
                                             "").replace("]",
                                                         "").replace(" ", "_")

        loop_counter = current_counter
        # loop through the tasks
        for counter, task_or_block in enumerate(block.block, 1):
            if isinstance(task_or_block, Block):
                loop_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=loop_counter,
                    play_vars=play_vars,
                    node_name_prefix=node_name_prefix,
                    tags=tags,
                    skip_tags=skip_tags)
            elif isinstance(task_or_block, TaskInclude):
                # here we have an `include_tasks` which is dynamic. So we need to process it explicitly because Ansible
                # does it during th execution of the playbook
                include_target = self.template(
                    task_or_block.args['_raw_params'],
                    play_vars,
                    fail_on_undefined=True)
                include_file = self.data_loader.path_dwim(include_target)
                data = self.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)

                # get the blocks from the include_tasks
                blocks = load_list_of_blocks(
                    data,
                    play=current_play,
                    variable_manager=self.variable_manager)

                for b in blocks:  # loop through the blocks inside the included tasks
                    loop_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=loop_counter,
                        play_vars=play_vars,
                        node_name_prefix=node_name_prefix,
                        tags=tags,
                        skip_tags=skip_tags)
            else:
                # check if the task should be included
                tagged = ''
                if not task_or_block.evaluate_tags(only_tags=tags,
                                                   skip_tags=skip_tags,
                                                   all_vars=play_vars):
                    tagged = NOT_TAGGED

                task_name = clean_name(
                    node_name_prefix +
                    self.template(task_or_block.get_name(), play_vars))
                task_id = id_prefix + clean_id(task_name + tagged)
                graph.node(task_name, shape="octagon", id=task_id)

                edge_id = "edge_" + parent_node_id + task_id + str(
                    loop_counter) + tagged

                graph.edge(parent_node_name,
                           task_name,
                           label=str(loop_counter + 1),
                           color=color,
                           fontcolor=color,
                           style="bold",
                           id=edge_id)
                self.graph_representation.add_link(parent_node_id, edge_id)
                self.graph_representation.add_link(edge_id, task_id)

                loop_counter += 1

        return loop_counter
Example #6
0
    def make_graph(self, include_role_tasks=False, tags=None, skip_tags=None):
        """
        Loop through the playbook and make the graph.

        The graph is drawn following this order (https://docs.ansible.com/ansible/2.4/playbooks_reuse_roles.html#using-roles)
        for each play:
            draw pre_tasks
            draw roles
                if  include_role_tasks
                    draw role_tasks
            draw tasks
            draw post_tasks
        :param include_role_tasks:
        :type include_role_tasks: bool
        :param tags:
        :type tags: list
        :param skip_tags:
        :type skip_tags: list
        :return:
        :rtype:
        """
        if tags is None:
            tags = ['all']

        if skip_tags is None:
            skip_tags = []

        # the root node
        self.graph.node(self.playbook_filename, style="dotted", id="root_node")

        # loop through the plays
        for play_counter, play in enumerate(self.playbook.get_plays(), 1):

            play_vars = self.variable_manager.get_vars(play)
            # get only the hosts name for the moment
            play_hosts = [
                h.get_name() for h in self.inventory_manager.get_hosts(
                    self.template(play.hosts, play_vars))
            ]
            nb_hosts = len(play_hosts)

            color, play_font_color = get_play_colors(play)

            play_name = "{} ({})".format(clean_name(str(play)), nb_hosts)

            play_name = self.template(play_name, play_vars)

            play_id = "play_" + clean_id(play_name)

            self.graph_representation.add_node(play_id)

            with self.graph.subgraph(name=play_name) as play_subgraph:

                # play node
                play_subgraph.node(play_name,
                                   id=play_id,
                                   style='filled',
                                   shape="box",
                                   color=color,
                                   fontcolor=play_font_color,
                                   tooltip="     ".join(play_hosts))

                # edge from root node to plays
                play_edge_id = "edge_" + clean_id(self.playbook_filename +
                                                  play_name +
                                                  str(play_counter))
                play_subgraph.edge(self.playbook_filename,
                                   play_name,
                                   id=play_edge_id,
                                   style="bold",
                                   label=str(play_counter),
                                   color=color,
                                   fontcolor=color)

                # loop through the pre_tasks
                nb_pre_tasks = 0
                for pre_task_block in play.pre_tasks:
                    nb_pre_tasks = self._include_tasks_in_blocks(
                        current_play=play,
                        graph=play_subgraph,
                        parent_node_name=play_name,
                        parent_node_id=play_id,
                        block=pre_task_block,
                        color=color,
                        current_counter=nb_pre_tasks,
                        play_vars=play_vars,
                        node_name_prefix='[pre_task] ',
                        tags=tags,
                        skip_tags=skip_tags)

                # loop through the roles
                for role_counter, role in enumerate(play.get_roles(), 1):
                    role_name = '[role] ' + clean_name(str(role))

                    # the role object doesn't inherit the tags from the play. So we add it manually
                    role.tags = role.tags + play.tags

                    role_not_tagged = ''
                    if not role.evaluate_tags(only_tags=tags,
                                              skip_tags=skip_tags,
                                              all_vars=play_vars):
                        role_not_tagged = NOT_TAGGED

                    with self.graph.subgraph(name=role_name,
                                             node_attr={}) as role_subgraph:
                        current_counter = role_counter + nb_pre_tasks
                        role_id = "role_" + clean_id(role_name +
                                                     role_not_tagged)
                        role_subgraph.node(role_name, id=role_id)

                        when = "".join(role.when)
                        play_to_node_label = str(current_counter) if len(
                            when) == 0 else str(
                                current_counter) + "  [when: " + when + "]"

                        edge_id = "edge_" + clean_id(play_id + role_id +
                                                     role_not_tagged)

                        role_subgraph.edge(play_name,
                                           role_name,
                                           label=play_to_node_label,
                                           color=color,
                                           fontcolor=color,
                                           id=edge_id)

                        self.graph_representation.add_link(play_id, edge_id)

                        self.graph_representation.add_link(edge_id, role_id)

                        # loop through the tasks of the roles
                        if include_role_tasks:
                            role_tasks_counter = 0
                            for block in role.get_task_blocks():
                                role_tasks_counter = self._include_tasks_in_blocks(
                                    current_play=play,
                                    graph=role_subgraph,
                                    parent_node_name=role_name,
                                    parent_node_id=role_id,
                                    block=block,
                                    color=color,
                                    play_vars=play_vars,
                                    current_counter=role_tasks_counter,
                                    node_name_prefix='[task] ',
                                    tags=tags,
                                    skip_tags=skip_tags)
                                role_tasks_counter += 1

                nb_roles = len(play.get_roles())
                # loop through the tasks
                nb_tasks = 0
                for task_block in play.tasks:
                    nb_tasks = self._include_tasks_in_blocks(
                        current_play=play,
                        graph=play_subgraph,
                        parent_node_name=play_name,
                        parent_node_id=play_id,
                        block=task_block,
                        color=color,
                        current_counter=nb_roles + nb_pre_tasks,
                        play_vars=play_vars,
                        node_name_prefix='[task] ',
                        tags=tags,
                        skip_tags=skip_tags)

                # loop through the post_tasks
                for post_task_block in play.post_tasks:
                    self._include_tasks_in_blocks(
                        current_play=play,
                        graph=play_subgraph,
                        parent_node_name=play_name,
                        parent_node_id=play_id,
                        block=post_task_block,
                        color=color,
                        current_counter=nb_tasks,
                        play_vars=play_vars,
                        node_name_prefix='[post_task] ',
                        tags=tags,
                        skip_tags=skip_tags)
Example #7
0
    def make_graph(self, *args, **kwargs):
        """
        Loop through the playbook and make the graph.

        The graph is drawn following this order (https://docs.ansible.com/ansible/2.4/playbooks_reuse_roles.html#using-roles)
        for each play:
            draw pre_tasks
            draw roles
                if  include_role_tasks
                    draw role_tasks
            draw tasks
            draw post_tasks
        :return:
        """

        # the root node
        self.graphiz_graph.node(self.playbook_filename, style="dotted", id="root_node")

        # loop through the plays
        for play_counter, play in enumerate(self.playbook.get_plays(), 1):

            # the load basedir is relative to the playbook path
            if play._included_path is not None:
                self.data_loader.set_basedir(play._included_path)
            else:
                self.data_loader.set_basedir(self.playbook._basedir)
            self.display.vvv(f"Loader basedir set to {self.data_loader.get_basedir()}")

            play_vars = self.variable_manager.get_vars(play)
            play_hosts = [h.get_name() for h in self.inventory_manager.get_hosts(self.template(play.hosts, play_vars))]
            play_name = "Play #{}: {} ({})".format(play_counter, clean_name(play.get_name()), len(play_hosts))
            play_name = self.template(play_name, play_vars)
            play_id = "play_" + generate_id()

            self.display.banner("Graphing " + play_name)

            self.graph_representation.add_node(play_id)

            with self.graphiz_graph.subgraph(name=play_name) as play_subgraph:
                color, play_font_color = get_play_colors(play)
                # play node
                play_subgraph.node(play_name, id=play_id, style="filled", shape="box", color=color,
                                   fontcolor=play_font_color, tooltip="     ".join(play_hosts))

                # edge from root node to plays
                play_edge_id = "edge_" + generate_id()
                play_subgraph.edge(self.playbook_filename, play_name, id=play_edge_id, style="bold",
                                   label=str(play_counter), color=color, fontcolor=color)

                # loop through the pre_tasks
                self.display.v("Graphing pre_tasks...")
                nb_pre_tasks = 0
                for pre_task_block in play.pre_tasks:
                    nb_pre_tasks = self._include_tasks_in_blocks(current_play=play, graph=play_subgraph,
                                                                 parent_node_name=play_name,
                                                                 parent_node_id=play_id,
                                                                 block=pre_task_block, color=color,
                                                                 current_counter=nb_pre_tasks,
                                                                 play_vars=play_vars,
                                                                 node_name_prefix="[pre_task] ")

                # global_tasks_counter will hold the number of pre_tasks + tasks + and post_tasks
                global_tasks_counter = nb_pre_tasks

                self.display.v(f"{global_tasks_counter} pre_task(s) added to the graph.")
                # loop through the roles
                self.display.v("Graphing roles...")
                role_number = 0
                for role in play.get_roles():
                    # Don't insert tasks from ``import/include_role``, preventing duplicate graphing
                    if role.from_include:
                        continue

                    # the role object doesn't inherit the tags from the play. So we add it manually.
                    role.tags = role.tags + play.tags
                    if not role.evaluate_tags(only_tags=self.tags, skip_tags=self.skip_tags,
                                              all_vars=play_vars):
                        self.display.vv(f"The role '{role.get_name()}' is skipped due to the tags.")
                        # Go to the next role
                        continue

                    role_number += 1
                    role_name = "[role] " + clean_name(role.get_name())

                    # edge from play to role
                    edge_id = "edge_" + generate_id()
                    play_subgraph.edge(play_name, role_name, label=str(role_number + global_tasks_counter), color=color,
                                       fontcolor=color, id=edge_id)
                    self.graph_representation.add_link(play_id, edge_id)

                    with self.graphiz_graph.subgraph(name=role_name, node_attr={}) as role_subgraph:

                        role_id = "role_" + generate_id()
                        role_subgraph.node(role_name, id=role_id)
                        self.graph_representation.add_link(edge_id, role_id)

                        # loop through the tasks of the roles
                        if self.include_role_tasks:
                            role_tasks_counter = 0  # the role tasks start a 0
                            for block in role.compile(play):
                                role_tasks_counter = self._include_tasks_in_blocks(current_play=play,
                                                                                   graph=role_subgraph,
                                                                                   parent_node_name=role_name,
                                                                                   parent_node_id=role_id, block=block,
                                                                                   color=color, play_vars=play_vars,
                                                                                   current_counter=role_tasks_counter,
                                                                                   node_name_prefix="[task] ")
                                role_tasks_counter += 1
                # end of roles loop
                self.display.v(f"{role_number} role(s) added to the graph")

                # loop through the tasks
                self.display.v("Graphing tasks...")
                for task_block in play.tasks:
                    global_tasks_counter = self._include_tasks_in_blocks(current_play=play, graph=play_subgraph,
                                                                         parent_node_name=play_name,
                                                                         parent_node_id=play_id,
                                                                         block=task_block, color=color,
                                                                         current_counter=role_number + global_tasks_counter,
                                                                         play_vars=play_vars,
                                                                         node_name_prefix="[task] ")
                nb_tasks = global_tasks_counter - role_number - nb_pre_tasks
                self.display.v(f"{nb_tasks} task(s) added to the graph.")

                # loop through the post_tasks
                self.display.v("Graphing post_tasks...")
                for post_task_block in play.post_tasks:
                    global_tasks_counter = self._include_tasks_in_blocks(current_play=play, graph=play_subgraph,
                                                                         parent_node_name=play_name,
                                                                         parent_node_id=play_id, block=post_task_block,
                                                                         color=color,
                                                                         current_counter=global_tasks_counter,
                                                                         play_vars=play_vars,
                                                                         node_name_prefix="[post_task] ")
                nb_post_tasks = global_tasks_counter - nb_tasks - role_number - nb_pre_tasks
                self.display.v(f"{nb_post_tasks} post_task(s) added to the graph.")

            self.display.banner(f"Done graphing {play_name}")
            self.display.display("")  # just an empty line
    def make_graph(self):
        """
        Loop through the playbook and make the graph.

        The graph is drawn following this order (https://docs.ansible.com/ansible/2.4/playbooks_reuse_roles.html#using-roles)
        for each play:
            draw pre_tasks
            draw roles
                if  include_role_tasks
                    draw role_tasks
            draw tasks
            draw post_tasks
        :return:
        :rtype:
        """

        # the root node
        self.graph.node(self.playbook_filename, style="dotted", id="root_node")

        # loop through the plays
        for play_counter, play in enumerate(self.playbook.get_plays(), 1):

            # the load basedir is relative to the playbook path
            if play._included_path is not None:
                self.data_loader.set_basedir(play._included_path)
            else:
                self.data_loader.set_basedir(self.playbook._basedir)
            self.display.vvv("Loader basedir set to {}".format(
                self.data_loader.get_basedir()))

            play_vars = self.variable_manager.get_vars(play)
            play_hosts = [
                h.get_name() for h in self.inventory_manager.get_hosts(
                    self.template(play.hosts, play_vars))
            ]
            play_name = "Play #{}: {} ({})".format(play_counter,
                                                   clean_name(play.get_name()),
                                                   len(play_hosts))
            play_name = self.template(play_name, play_vars)

            self.display.banner("Graphing " + play_name)

            play_id = "play_" + str(uuid.uuid4())

            self.graph_representation.add_node(play_id)

            with self.graph.subgraph(name=play_name) as play_subgraph:
                color, play_font_color = get_play_colors(play)
                # play node
                play_subgraph.node(play_name,
                                   id=play_id,
                                   style="filled",
                                   shape="box",
                                   color=color,
                                   fontcolor=play_font_color,
                                   tooltip="     ".join(play_hosts))

                # edge from root node to plays
                play_edge_id = "edge_" + str(uuid.uuid4())
                play_subgraph.edge(self.playbook_filename,
                                   play_name,
                                   id=play_edge_id,
                                   style="bold",
                                   label=str(play_counter),
                                   color=color,
                                   fontcolor=color)

                # loop through the pre_tasks
                self.display.v("Graphing pre_tasks...")
                nb_pre_tasks = 0
                for pre_task_block in play.pre_tasks:
                    nb_pre_tasks = self._include_tasks_in_blocks(
                        current_play=play,
                        graph=play_subgraph,
                        parent_node_name=play_name,
                        parent_node_id=play_id,
                        block=pre_task_block,
                        color=color,
                        current_counter=nb_pre_tasks,
                        play_vars=play_vars,
                        node_name_prefix="[pre_task] ")

                # loop through the roles
                self.display.v("Graphing roles...")
                role_number = 0
                for role in play.get_roles():
                    # Don't insert tasks from ``import/include_role``, preventing
                    # duplicate graphing
                    if role.from_include:
                        continue

                    role_number += 1

                    role_name = "[role] " + clean_name(role.get_name())

                    # the role object doesn't inherit the tags from the play. So we add it manually
                    role.tags = role.tags + play.tags

                    role_not_tagged = ""
                    if not role.evaluate_tags(only_tags=self.options.tags,
                                              skip_tags=self.options.skip_tags,
                                              all_vars=play_vars):
                        role_not_tagged = NOT_TAGGED

                    with self.graph.subgraph(name=role_name,
                                             node_attr={}) as role_subgraph:
                        current_counter = role_number + nb_pre_tasks
                        role_id = "role_" + str(uuid.uuid4()) + role_not_tagged
                        role_subgraph.node(role_name, id=role_id)

                        edge_id = "edge_" + str(uuid.uuid4()) + role_not_tagged

                        # edge from play to role
                        role_subgraph.edge(play_name,
                                           role_name,
                                           label=str(current_counter),
                                           color=color,
                                           fontcolor=color,
                                           id=edge_id)

                        self.graph_representation.add_link(play_id, edge_id)
                        self.graph_representation.add_link(edge_id, role_id)

                        # loop through the tasks of the roles
                        if self.options.include_role_tasks:
                            role_tasks_counter = 0
                            for block in role.compile(play):
                                role_tasks_counter = self._include_tasks_in_blocks(
                                    current_play=play,
                                    graph=role_subgraph,
                                    parent_node_name=role_name,
                                    parent_node_id=role_id,
                                    block=block,
                                    color=color,
                                    play_vars=play_vars,
                                    current_counter=role_tasks_counter,
                                    node_name_prefix="[task] ")
                                role_tasks_counter += 1
                self.display.v(
                    "{} roles added to the graph".format(role_number))

                # loop through the tasks
                self.display.v("Graphing tasks...")
                nb_tasks = 0
                for task_block in play.tasks:
                    nb_tasks = self._include_tasks_in_blocks(
                        current_play=play,
                        graph=play_subgraph,
                        parent_node_name=play_name,
                        parent_node_id=play_id,
                        block=task_block,
                        color=color,
                        current_counter=role_number + nb_pre_tasks,
                        play_vars=play_vars,
                        node_name_prefix="[task] ")

                # loop through the post_tasks
                self.display.v("Graphing post_tasks...")
                for post_task_block in play.post_tasks:
                    self._include_tasks_in_blocks(
                        current_play=play,
                        graph=play_subgraph,
                        parent_node_name=play_name,
                        parent_node_id=play_id,
                        block=post_task_block,
                        color=color,
                        current_counter=nb_tasks,
                        play_vars=play_vars,
                        node_name_prefix="[post_task] ")

            self.display.banner("Done graphing {}".format(play_name))
            self.display.display("")  # just an empty line
Example #9
0
    def parse(self, *args, **kwargs) -> PlaybookNode:
        """
        Loop through the playbook and generate the graph.

        The graph is drawn following this order (https://docs.ansible.com/ansible/2.4/playbooks_reuse_roles.html#using-roles)
        for each play:
            add pre_tasks
            add roles
                if  include_role_tasks
                    add role_tasks
            add tasks
            add post_tasks
        :return:
        """

        playbook = Playbook.load(
            self.playbook_filename,
            loader=self.data_loader,
            variable_manager=self.variable_manager,
        )
        # the root node
        playbook_root_node = PlaybookNode(self.playbook_filename,
                                          raw_object=playbook)
        # loop through the plays
        for play in playbook.get_plays():

            # the load basedir is relative to the playbook path
            if play._included_path is not None:
                self.data_loader.set_basedir(play._included_path)
            else:
                self.data_loader.set_basedir(playbook._basedir)
            display.vvv(
                f"Loader basedir set to {self.data_loader.get_basedir()}")

            play_vars = self.variable_manager.get_vars(play)
            play_hosts = [
                h.get_name() for h in self.inventory_manager.get_hosts(
                    self.template(play.hosts, play_vars))
            ]
            play_name = f"Play: {clean_name(play.get_name())} ({len(play_hosts)})"
            play_name = self.template(play_name, play_vars)

            display.v(f"Parsing {play_name}")

            play_node = PlayNode(play_name, hosts=play_hosts, raw_object=play)
            playbook_root_node.add_node("plays", play_node)

            # loop through the pre_tasks
            display.v("Parsing pre_tasks...")
            for pre_task_block in play.pre_tasks:
                self._include_tasks_in_blocks(
                    current_play=play,
                    parent_nodes=[play_node],
                    block=pre_task_block,
                    play_vars=play_vars,
                    node_type="pre_task",
                )

            # loop through the roles
            display.v("Parsing roles...")

            for role in play.get_roles():
                # Don't insert tasks from ``import/include_role``, preventing duplicate graphing
                if role.from_include:
                    continue

                # the role object doesn't inherit the tags from the play. So we add it manually.
                role.tags = role.tags + play.tags
                if not role.evaluate_tags(only_tags=self.tags,
                                          skip_tags=self.skip_tags,
                                          all_vars=play_vars):
                    display.vv(
                        f"The role '{role.get_name()}' is skipped due to the tags."
                    )
                    # Go to the next role
                    continue

                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(role.get_name())
                else:
                    # Otherwise, a random id is used
                    role_node_id = generate_id("role_")
                role_node = RoleNode(
                    clean_name(role.get_name()),
                    node_id=role_node_id,
                    raw_object=role,
                    parent=play_node,
                )
                # edge from play to role
                play_node.add_node("roles", role_node)

                if self.include_role_tasks:
                    # loop through the tasks of the roles
                    for block in role.compile(play):
                        self._include_tasks_in_blocks(
                            current_play=play,
                            parent_nodes=[role_node],
                            block=block,
                            play_vars=play_vars,
                            node_type="task",
                        )
                    # end of roles loop

            # loop through the tasks
            display.v("Parsing tasks...")
            for task_block in play.tasks:
                self._include_tasks_in_blocks(
                    current_play=play,
                    parent_nodes=[play_node],
                    block=task_block,
                    play_vars=play_vars,
                    node_type="task",
                )

            # loop through the post_tasks
            display.v("Parsing post_tasks...")
            for post_task_block in play.post_tasks:
                self._include_tasks_in_blocks(
                    current_play=play,
                    parent_nodes=[play_node],
                    block=post_task_block,
                    play_vars=play_vars,
                    node_type="post_task",
                )
            # Summary
            display.v(
                f"{len(play_node.pre_tasks)} pre_task(s) added to the graph.")
            display.v(f"{len(play_node.roles)} role(s) added to the play")
            display.v(f"{len(play_node.tasks)} task(s) added to the play")
            display.v(
                f"{len(play_node.post_tasks)} post_task(s) added to the play")
            # moving to the next play

        return playbook_root_node
Example #10
0
    def _include_tasks_in_blocks(self,
                                 graph,
                                 parent_node_name,
                                 parent_node_id,
                                 block,
                                 color,
                                 current_counter,
                                 variables=None,
                                 node_name_prefix='',
                                 tags=None,
                                 skip_tags=None):
        """
       Recursively read all the tasks of the block and add it to the graph
       :param variables:
       :param tags:
       :param parent_node_id:
       :param graph_representation:
       :param node_name_prefix:
       :param color:
       :param current_counter:
       :param graph:
       :param parent_node_name:
       :param block:
       :return:
       """
        if tags is None:
            tags = ['all']

        if skip_tags is None:
            skip_tags = []

        loop_counter = current_counter
        # loop through the tasks
        for counter, task_or_block in enumerate(block.block, 1):
            if isinstance(task_or_block, Block):
                loop_counter = self._include_tasks_in_blocks(
                    graph, parent_node_name, parent_node_id, task_or_block,
                    color, loop_counter, variables, node_name_prefix, tags,
                    skip_tags)
            else:

                # check if the task should be included
                tagged = ''
                if not task_or_block.evaluate_tags(only_tags=tags,
                                                   skip_tags=skip_tags,
                                                   all_vars=variables):
                    tagged = NOT_TAGGED

                task_name = clean_name(
                    node_name_prefix +
                    self.template(task_or_block.get_name(), variables))
                task_id = clean_id(task_name + tagged)
                graph.node(task_name, shape="octagon", id=task_id)

                edge_id = "edge_" + parent_node_id + task_id + str(
                    loop_counter) + tagged

                graph.edge(parent_node_name,
                           task_name,
                           label=str(loop_counter + 1),
                           color=color,
                           fontcolor=color,
                           style="bold",
                           id=edge_id)
                self.graph_representation.add_link(parent_node_id, edge_id)
                self.graph_representation.add_link(edge_id, task_id)

                loop_counter += 1

        return loop_counter