def parse( self, include_role_tasks: bool = False, tags: List[str] = None, skip_tags: List[str] = None, group_roles_by_name: bool = False, ): """ Parses all the provided playbooks :param include_role_tasks: Should we include the role tasks :param tags: Only add plays and tasks tagged with these values :param skip_tags: Only add plays and tasks whose tags do not match these values :param group_roles_by_name: Group roles by name instead of considering them as separate nodes with different IDs :return: """ for playbook_file in self.playbook_filenames: display.display(f"Parsing playbook {playbook_file}") parser = PlaybookParser( tags=tags, skip_tags=skip_tags, playbook_filename=playbook_file, include_role_tasks=include_role_tasks, group_roles_by_name=group_roles_by_name, ) playbook_node = parser.parse() self.playbook_nodes.append(playbook_node) # Setting colors for play for play in playbook_node.plays: # TODO: find a way to create visual distance between the generated colors # https://stackoverflow.com/questions/9018016/how-to-compare-two-colors-for-similarity-difference self.plays_color[play] = get_play_colors(play.id) # Update the usage of the roles self.roles_usage = merge_dicts(self.roles_usage, playbook_node.roles_usage())
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)
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