mirror of
https://github.com/haidaraM/ansible-playbook-grapher
synced 2024-11-10 06:04:15 +00:00
feat: Add ability do distinguish the explicit block on the graph (#86)
This commit is contained in:
parent
9a0fdec592
commit
77d3f2bd6a
8 changed files with 192 additions and 83 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -10,11 +10,14 @@
|
|||
- feat: Curved edge label based on the path [\#84](https://github.com/haidaraM/ansible-playbook-grapher/pull/84)
|
||||
- feat: Add option to automatically view the generated
|
||||
file [\#88](https://github.com/haidaraM/ansible-playbook-grapher/pull/88)
|
||||
- feat: Add support for block [\#86](https://github.com/haidaraM/ansible-playbook-grapher/pull/86)
|
||||
- fix:
|
||||
- front: Refactor the JS part and fix issue when selecting/unselecting nodes
|
||||
- front: Do not unhighlight the current selected node when hovering on parent node
|
||||
- cli(typo): rename `--ouput-file-name` to `--output-file-name`
|
||||
- fix: Use the correct tooltip for edges
|
||||
- front: Refactor the JS part and fix issue when selecting/unselecting nodes
|
||||
- front: Do not unhighlight the current selected node when hovering on parent node
|
||||
- cli(typo): rename `--ouput-file-name` to `--output-file-name`
|
||||
- Use the correct tooltip for edges
|
||||
- style: Do not use bold style by default and apply color on nodes border
|
||||
- Merge when condition with `and`
|
||||
- test:
|
||||
- Add Ansible 2.10.7 in the test matrix
|
||||
- Make test verbose by default with `-vv` in the args
|
||||
|
|
|
@ -100,6 +100,7 @@ function clickOnElement(event) {
|
|||
$("#svg").ready(function () {
|
||||
let plays = $("g[id^=play_]");
|
||||
let roles = $("g[id^=role_]");
|
||||
let blocks = $("g[id^=block_]");
|
||||
|
||||
// Set hover and click events on the plays
|
||||
plays.hover(hoverMouseEnter, hoverMouseLeave);
|
||||
|
@ -109,4 +110,8 @@ $("#svg").ready(function () {
|
|||
roles.hover(hoverMouseEnter, hoverMouseLeave);
|
||||
roles.click(clickOnElement);
|
||||
|
||||
// Set hover and click events on the blocks
|
||||
blocks.hover(hoverMouseEnter, hoverMouseLeave);
|
||||
blocks.click(clickOnElement);
|
||||
|
||||
});
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from abc import ABC
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List
|
||||
|
||||
from ansibleplaybookgrapher.utils import generate_id
|
||||
|
||||
|
||||
class Node(ABC):
|
||||
class Node:
|
||||
"""
|
||||
A node in the graph. Everything of the final graph is a node: playbook, plays, edges, tasks and roles.
|
||||
"""
|
||||
|
@ -29,13 +28,14 @@ class CompositeNode(Node):
|
|||
A node that composed of multiple of nodes.
|
||||
"""
|
||||
|
||||
def __init__(self, node_name: str, node_id: str):
|
||||
def __init__(self, node_name: str, node_id: str, supported_compositions: List[str] = None):
|
||||
"""
|
||||
|
||||
:param node_name:
|
||||
:param node_id:
|
||||
"""
|
||||
super().__init__(node_name, node_id)
|
||||
self._supported_compositions = supported_compositions or []
|
||||
# The dict will contain the different types of composition.
|
||||
self._compositions = defaultdict(list) # type: Dict[str, List]
|
||||
|
||||
|
@ -54,6 +54,9 @@ class CompositeNode(Node):
|
|||
:param node: The node to add in the given composition
|
||||
:return:
|
||||
"""
|
||||
if target_composition not in self._supported_compositions:
|
||||
raise Exception(
|
||||
f"The target composition '{target_composition}' is unknown. Supported are: {self._supported_compositions}")
|
||||
self._compositions[target_composition].append(node)
|
||||
|
||||
def links_structure(self) -> Dict[Node, List[Node]]:
|
||||
|
@ -84,7 +87,7 @@ class PlaybookNode(CompositeNode):
|
|||
"""
|
||||
|
||||
def __init__(self, node_name: str, plays: List['PlayNode'] = None, node_id: str = None):
|
||||
super().__init__(node_name, node_id or generate_id("playbook_"))
|
||||
super().__init__(node_name, node_id or generate_id("playbook_"), ["plays"])
|
||||
self._compositions['plays'] = plays or []
|
||||
|
||||
@property
|
||||
|
@ -122,7 +125,7 @@ class PlayNode(CompositeNode):
|
|||
:param node_id:
|
||||
:param hosts: List of hosts attached to the play
|
||||
"""
|
||||
super().__init__(node_name, node_id or generate_id("play_"))
|
||||
super().__init__(node_name, node_id or generate_id("play_"), ["pre_tasks", "roles", "tasks", "post_tasks"])
|
||||
self.hosts = hosts or []
|
||||
|
||||
@property
|
||||
|
@ -142,6 +145,23 @@ class PlayNode(CompositeNode):
|
|||
return self._compositions["tasks"]
|
||||
|
||||
|
||||
class BlockNode(CompositeNode):
|
||||
"""
|
||||
A block node: https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html
|
||||
"""
|
||||
|
||||
def __init__(self, node_name: str, node_id: str = None):
|
||||
super().__init__(node_name, node_id or generate_id("block_"), ["tasks"])
|
||||
|
||||
@property
|
||||
def tasks(self) -> List['EdgeNode']:
|
||||
"""
|
||||
The tasks attached to this block
|
||||
:return:
|
||||
"""
|
||||
return self._compositions['tasks']
|
||||
|
||||
|
||||
class EdgeNode(CompositeNode):
|
||||
"""
|
||||
An edge between two nodes. It's a special case of composite node with only one composition with one element
|
||||
|
@ -155,9 +175,9 @@ class EdgeNode(CompositeNode):
|
|||
:param destination: The edge destination node
|
||||
:param node_id: The edge id
|
||||
"""
|
||||
super().__init__(node_name, node_id or generate_id("edge_"))
|
||||
super().__init__(node_name, node_id or generate_id("edge_"), ["destination"])
|
||||
self.source = source
|
||||
self.add_node("nodes", destination)
|
||||
self.add_node("destination", destination)
|
||||
|
||||
def add_node(self, target_composition: str, node: Node):
|
||||
"""
|
||||
|
@ -177,7 +197,7 @@ class EdgeNode(CompositeNode):
|
|||
Return the destination of the edge
|
||||
:return:
|
||||
"""
|
||||
return self._compositions["nodes"][0]
|
||||
return self._compositions["destination"][0]
|
||||
|
||||
|
||||
class TaskNode(Node):
|
||||
|
@ -195,7 +215,7 @@ class RoleNode(CompositeNode):
|
|||
"""
|
||||
|
||||
def __init__(self, node_name: str, node_id: str = None):
|
||||
super().__init__(node_name, node_id or generate_id("role_"))
|
||||
super().__init__(node_name, node_id or generate_id("role_"), ["tasks"])
|
||||
|
||||
@property
|
||||
def tasks(self):
|
||||
|
|
|
@ -13,8 +13,9 @@ from ansible.playbook.task_include import TaskInclude
|
|||
from ansible.template import Templar
|
||||
from ansible.utils.display import Display
|
||||
|
||||
from ansibleplaybookgrapher.graph import EdgeNode, TaskNode, PlaybookNode, RoleNode, PlayNode, CompositeNode
|
||||
from ansibleplaybookgrapher.utils import clean_name, handle_include_path, has_role_parent, generate_id
|
||||
from ansibleplaybookgrapher.graph import EdgeNode, TaskNode, PlaybookNode, RoleNode, PlayNode, CompositeNode, BlockNode
|
||||
from ansibleplaybookgrapher.utils import clean_name, handle_include_path, has_role_parent, generate_id, \
|
||||
convert_when_to_str
|
||||
|
||||
|
||||
class BaseParser(ABC):
|
||||
|
@ -73,12 +74,8 @@ class BaseParser(ABC):
|
|||
|
||||
self.display.vv(f"Adding {node_type} '{task.get_name()}' to the graph")
|
||||
|
||||
edge_label = ""
|
||||
if len(task.when) > 0:
|
||||
when = "".join(map(str, task.when))
|
||||
edge_label += " [when: " + when + "]"
|
||||
|
||||
task_name = clean_name(f"[{node_type}] " + self.template(task.get_name(), task_vars))
|
||||
edge_label = convert_when_to_str(task.when)
|
||||
|
||||
edge_node = EdgeNode(parent_node, TaskNode(task_name, generate_id(f"{node_type}_")), edge_label)
|
||||
parent_node.add_node(target_composition=f"{node_type}s", node=edge_node)
|
||||
|
@ -214,6 +211,13 @@ class PlaybookParser(BaseParser):
|
|||
: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))
|
||||
parent_nodes[-1].add_node(f"{node_type}s",
|
||||
EdgeNode(parent_nodes[-1], block_node, convert_when_to_str(block.when)))
|
||||
parent_nodes.append(block_node)
|
||||
|
||||
# loop through the tasks
|
||||
for task_or_block in block.block:
|
||||
if isinstance(task_or_block, Block):
|
||||
|
@ -233,7 +237,7 @@ class PlaybookParser(BaseParser):
|
|||
|
||||
role_node = RoleNode(task_or_block.args['name'])
|
||||
# TODO: add support for conditional (when) for include_role in the edge label
|
||||
parent_nodes[-1].add_node("roles", EdgeNode(parent_nodes[-1], role_node))
|
||||
parent_nodes[-1].add_node(f"{node_type}s", EdgeNode(parent_nodes[-1], role_node))
|
||||
|
||||
if self.include_role_tasks:
|
||||
# If we have an include_role and we want to include role tasks, the parent node now becomes
|
||||
|
@ -274,9 +278,12 @@ class PlaybookParser(BaseParser):
|
|||
self._include_tasks_in_blocks(current_play=current_play, parent_nodes=parent_nodes, block=b,
|
||||
play_vars=task_vars, node_type=node_type)
|
||||
else:
|
||||
if len(parent_nodes) > 1 and not has_role_parent(task_or_block):
|
||||
# We add a new parent node only if we found an include_role. If an include_role is not found, and we
|
||||
# have a task that is not from an include_role, we remove the last RoleNode we have added.
|
||||
if len(parent_nodes) > 1 and not has_role_parent(task_or_block) and task_or_block._parent._implicit:
|
||||
# We add a new parent node if:
|
||||
# - We found an include_role
|
||||
# - We found an explicit Block
|
||||
# If an include_role is not found and we have a task that is not from an include_role and not from
|
||||
# an explicit block => we remove the last CompositeNode we have added.
|
||||
parent_nodes.pop()
|
||||
|
||||
# check if this task comes from a role, and we don't want to include tasks of the role
|
||||
|
|
|
@ -4,7 +4,7 @@ from typing import Dict
|
|||
from ansible.utils.display import Display
|
||||
from graphviz import Digraph
|
||||
|
||||
from ansibleplaybookgrapher.graph import PlaybookNode, EdgeNode, Node, PlayNode, RoleNode
|
||||
from ansibleplaybookgrapher.graph import PlaybookNode, EdgeNode, PlayNode, RoleNode, BlockNode
|
||||
from ansibleplaybookgrapher.utils import clean_name, get_play_colors
|
||||
|
||||
|
||||
|
@ -40,27 +40,89 @@ class GraphvizRenderer:
|
|||
"""
|
||||
self.display = display
|
||||
self.playbook_node = playbook_node
|
||||
self.graphviz = GraphvizCustomDigraph(format=graph_format,
|
||||
graph_attr=graph_attr or GraphvizRenderer.DEFAULT_GRAPH_ATTR,
|
||||
edge_attr=edge_attr or GraphvizRenderer.DEFAULT_EDGE_ATTR)
|
||||
self.digraph = GraphvizCustomDigraph(format=graph_format,
|
||||
graph_attr=graph_attr or GraphvizRenderer.DEFAULT_GRAPH_ATTR,
|
||||
edge_attr=edge_attr or GraphvizRenderer.DEFAULT_EDGE_ATTR)
|
||||
|
||||
def _add_task(self, graph: GraphvizCustomDigraph, parent_node: Node, edge: EdgeNode, color: str, task_counter: int,
|
||||
shape: str = "octagon"):
|
||||
def render_node(self, graph: GraphvizCustomDigraph, edge: EdgeNode, color: str, node_counter: int,
|
||||
shape: str = "octagon"):
|
||||
"""
|
||||
Add a task in the given graph
|
||||
:param graph:
|
||||
:param parent_node
|
||||
:param edge:
|
||||
:param color
|
||||
:param shape
|
||||
Render a generic node in the graph
|
||||
:param graph: The graph to render the node to
|
||||
:param edge: The edge from a node to the Node
|
||||
:param color: The color to apply
|
||||
:param node_counter: The counter for this node
|
||||
:param shape: the default shape of the node
|
||||
:return:
|
||||
"""
|
||||
destination_node = edge.destination
|
||||
graph.node(destination_node.id, label=destination_node.name, shape=shape, id=destination_node.id,
|
||||
tooltip=destination_node.name)
|
||||
edge_label = f"{task_counter} {edge.name}"
|
||||
graph.edge(parent_node.id, destination_node.id, label=edge_label, color=color, fontcolor=color, style="bold",
|
||||
id=edge.id, tooltip=edge_label, labeltooltip=edge_label)
|
||||
source_node = edge.source
|
||||
|
||||
if isinstance(destination_node, BlockNode):
|
||||
self.render_block(graph, node_counter, edge, color)
|
||||
elif isinstance(destination_node, RoleNode):
|
||||
self.render_role(graph, node_counter, edge, color)
|
||||
else:
|
||||
edge_label = f"{node_counter} {edge.name}"
|
||||
graph.node(destination_node.id, label=destination_node.name, shape=shape, id=destination_node.id,
|
||||
tooltip=destination_node.name, color=color)
|
||||
graph.edge(source_node.id, destination_node.id, label=edge_label, color=color, fontcolor=color, id=edge.id,
|
||||
tooltip=edge_label, labeltooltip=edge_label)
|
||||
|
||||
def render_block(self, graph: Digraph, edge_counter: int, edge: EdgeNode, color: str, **kwargs):
|
||||
"""
|
||||
Render a block in the graph.
|
||||
A BlockNode is a special node: a cluster is created instead of a normal node.
|
||||
:param graph: The graph to render the block into
|
||||
:param edge_counter: The counter for this edge in the graph
|
||||
:param edge: The edge from a node to the BlockNode
|
||||
:param color: The color to apply
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
destination_node = edge.destination # type: BlockNode
|
||||
edge_label = f"{edge_counter}"
|
||||
if graph.name.startswith("cluster"):
|
||||
# We are rendering a block inside another block.
|
||||
# In that case, we add the edge name as label also to materialize the when condition
|
||||
edge_label = f"{edge_counter} {edge.name}"
|
||||
|
||||
# BlockNode is a special node: a cluster is created instead of a normal node
|
||||
with graph.subgraph(name=f"cluster_{destination_node.id}") as block_subgraph:
|
||||
|
||||
block_subgraph.node(destination_node.id, label=f"[block] {destination_node.name}", shape="box",
|
||||
id=destination_node.id, tooltip=destination_node.name, color=color,
|
||||
labeltooltip=destination_node.name)
|
||||
graph.edge(edge.source.id, destination_node.id, label=edge_label, color=color, fontcolor=color,
|
||||
tooltip=edge_label, id=edge.id, labeltooltip=edge_label)
|
||||
|
||||
for b_counter, task_edge_node in enumerate(destination_node.tasks, 1):
|
||||
self.render_node(block_subgraph, task_edge_node, color, b_counter)
|
||||
|
||||
def render_role(self, graph: Digraph, edge_counter: int, edge: EdgeNode, color: str, **kwargs):
|
||||
"""
|
||||
Render a role in the graph
|
||||
:param graph: The graph to render the role into
|
||||
:param edge_counter: The counter for this edge in the graph
|
||||
:param edge: The edge from a node to the RoleNode
|
||||
:param color: The color to apply
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
role = edge.destination # type: RoleNode
|
||||
role_edge_label = f"{edge_counter} {edge.name}"
|
||||
|
||||
with self.digraph.subgraph(name=role.name, node_attr={}) as role_subgraph:
|
||||
role_subgraph.node(role.id, id=role.id, label=f"[role] {role.name}", tooltip=role.name, color=color)
|
||||
# from parent to role
|
||||
graph.edge(edge.source.id, role.id, label=role_edge_label, color=color, fontcolor=color, id=edge.id,
|
||||
tooltip=role_edge_label, labeltooltip=role_edge_label)
|
||||
|
||||
# role tasks
|
||||
for role_task_counter, role_task_edge in enumerate(role.tasks, 1):
|
||||
self.render_node(role_subgraph, role_task_edge, color, node_counter=role_task_counter)
|
||||
|
||||
def _convert_to_graphviz(self):
|
||||
"""
|
||||
|
@ -68,56 +130,41 @@ class GraphvizRenderer:
|
|||
:return:
|
||||
"""
|
||||
# root node
|
||||
self.graphviz.node(self.playbook_node.name, style="dotted", id="root_node")
|
||||
self.digraph.node(self.playbook_node.name, style="dotted", id="root_node")
|
||||
|
||||
for play_counter, play_edge in enumerate(self.playbook_node.plays, 1):
|
||||
# noinspection PyTypeChecker
|
||||
play = play_edge.destination # type: PlayNode
|
||||
with self.graphviz.subgraph(name=play.name) as play_subgraph:
|
||||
with self.digraph.subgraph(name=play.name) as play_subgraph:
|
||||
color, play_font_color = get_play_colors(play)
|
||||
# play node
|
||||
play_tooltip = ",".join(play.hosts) if len(play.hosts) > 0 else play.name
|
||||
self.graphviz.node(play.id, id=play.id, label=play.name, style="filled", shape="box", color=color,
|
||||
fontcolor=play_font_color, tooltip=play_tooltip)
|
||||
self.digraph.node(play.id, id=play.id, label=play.name, style="filled", shape="box", color=color,
|
||||
fontcolor=play_font_color, tooltip=play_tooltip)
|
||||
# edge from root node to play
|
||||
playbook_to_play_label = f"{play_counter} {play_edge.name}"
|
||||
self.graphviz.edge(self.playbook_node.name, play.id, id=play_edge.id, style="bold",
|
||||
label=playbook_to_play_label, color=color, fontcolor=color,
|
||||
tooltip=playbook_to_play_label, labeltooltip=playbook_to_play_label)
|
||||
self.digraph.edge(self.playbook_node.name, play.id, id=play_edge.id, label=playbook_to_play_label,
|
||||
color=color, fontcolor=color, tooltip=playbook_to_play_label,
|
||||
labeltooltip=playbook_to_play_label)
|
||||
|
||||
# pre_tasks
|
||||
for pre_task_counter, pre_task_edge in enumerate(play.pre_tasks, 1):
|
||||
self._add_task(graph=play_subgraph, parent_node=play, edge=pre_task_edge, color=color,
|
||||
task_counter=pre_task_counter)
|
||||
self.render_node(play_subgraph, pre_task_edge, color, node_counter=pre_task_counter)
|
||||
|
||||
# roles
|
||||
for role_counter, role_edge in enumerate(play.roles, 1):
|
||||
# noinspection PyTypeChecker
|
||||
role = role_edge.destination # type: RoleNode
|
||||
role_edge_label = f"{role_counter + len(play.pre_tasks)} {role_edge.name}"
|
||||
|
||||
with self.graphviz.subgraph(name=role.name, node_attr={}) as role_subgraph:
|
||||
# from play to role
|
||||
role_subgraph.node(role.id, id=role.id, label=f"[role] {role.name}", tooltip=role.name)
|
||||
|
||||
play_subgraph.edge(play.id, role.id, label=role_edge_label, color=color, fontcolor=color,
|
||||
style="bold", id=role_edge.id, tooltip=role_edge_label,
|
||||
labeltooltip=role_edge_label)
|
||||
|
||||
# role tasks
|
||||
for role_task_counter, role_task_edge in enumerate(role.tasks, 1):
|
||||
self._add_task(role_subgraph, role, role_task_edge, color, task_counter=role_task_counter)
|
||||
self.render_role(self.digraph, role_counter + len(play.pre_tasks), role_edge, color)
|
||||
|
||||
# tasks
|
||||
for task_counter, task_edge in enumerate(play.tasks, 1):
|
||||
self._add_task(play_subgraph, play, task_edge, color,
|
||||
task_counter=len(play.pre_tasks) + len(play.roles) + task_counter)
|
||||
self.render_node(play_subgraph, task_edge, color,
|
||||
node_counter=len(play.pre_tasks) + len(play.roles) + task_counter)
|
||||
|
||||
# post_tasks
|
||||
for post_task_counter, post_task_edge in enumerate(play.post_tasks, 1):
|
||||
self._add_task(play_subgraph, play, post_task_edge, color,
|
||||
task_counter=len(play.pre_tasks) + len(play.roles) + len(
|
||||
play.tasks) + post_task_counter)
|
||||
self.render_node(play_subgraph, post_task_edge, color,
|
||||
node_counter=len(play.pre_tasks) + len(play.roles) + len(
|
||||
play.tasks) + post_task_counter)
|
||||
|
||||
def render(self, output_filename: str, save_dot_file=False, view=False) -> str:
|
||||
"""
|
||||
|
@ -129,8 +176,8 @@ class GraphvizRenderer:
|
|||
:return: The rendered file path (output_filename.svg)
|
||||
"""
|
||||
self._convert_to_graphviz()
|
||||
rendered_file_path = self.graphviz.render(cleanup=not save_dot_file, format="svg", filename=output_filename,
|
||||
view=view)
|
||||
rendered_file_path = self.digraph.render(cleanup=not save_dot_file, format="svg", filename=output_filename,
|
||||
view=view)
|
||||
|
||||
if save_dot_file:
|
||||
# add .dot extension. The render doesn't add an extension
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import uuid
|
||||
from typing import Tuple
|
||||
from typing import Tuple, List
|
||||
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
from ansible.parsing.yaml.objects import AnsibleUnicode
|
||||
from ansible.playbook.role_include import IncludeRole
|
||||
from ansible.playbook.task import Task
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
|
@ -10,6 +11,16 @@ from ansible.template import Templar
|
|||
from colour import Color
|
||||
|
||||
|
||||
def convert_when_to_str(when: List[AnsibleUnicode]) -> str:
|
||||
"""
|
||||
Convert ansible conditional when to str
|
||||
:param when:
|
||||
:return:
|
||||
"""
|
||||
|
||||
return f"[when: {' and '.join(when)}]" if len(when) > 0 else ""
|
||||
|
||||
|
||||
def generate_id(prefix: str = "") -> str:
|
||||
"""
|
||||
Generate an uuid to be used as id
|
||||
|
|
14
tests/fixtures/with_block.yml
vendored
14
tests/fixtures/with_block.yml
vendored
|
@ -1,7 +1,11 @@
|
|||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Install tree
|
||||
yum:
|
||||
name: tree
|
||||
- name: Install Apache
|
||||
when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6")
|
||||
block:
|
||||
- name: Install some packages
|
||||
yum:
|
||||
|
@ -13,7 +17,15 @@
|
|||
- template:
|
||||
src: templates/src.j2
|
||||
dest: /etc/foo.conf
|
||||
- block:
|
||||
- get_url: url={{ remote_database_dump }} dest={{ local_database_dump }}
|
||||
when: "True"
|
||||
- command: pg_restore -d {{ dbname }} {{ local_database_dump }}
|
||||
- service:
|
||||
name: bar
|
||||
state: started
|
||||
enabled: True
|
||||
enabled: True
|
||||
post_tasks:
|
||||
- name: Debug
|
||||
debug:
|
||||
msg: "Debug 1"
|
||||
|
|
|
@ -45,7 +45,7 @@ def run_grapher(playbook_file: str, output_filename: str = None, additional_args
|
|||
|
||||
def _common_tests(svg_path: str, playbook_path: str, plays_number: int = 0, tasks_number: int = 0,
|
||||
post_tasks_number: int = 0, roles_number: int = 0,
|
||||
pre_tasks_number: int = 0) -> Dict[str, List[Element]]:
|
||||
pre_tasks_number: int = 0, blocks_number: int = 0) -> Dict[str, List[Element]]:
|
||||
"""
|
||||
Perform some common tests on the generated svg file:
|
||||
- Existence of svg file
|
||||
|
@ -70,19 +70,22 @@ def _common_tests(svg_path: str, playbook_path: str, plays_number: int = 0, task
|
|||
tasks = pq("g[id^='task_']")
|
||||
post_tasks = pq("g[id^='post_task_']")
|
||||
pre_tasks = pq("g[id^='pre_task_']")
|
||||
blocks = pq("g[id^='block_']")
|
||||
roles = pq("g[id^='role_']")
|
||||
|
||||
assert plays_number == len(plays), "The playbook '{}' should contains {} play(s) but we found {} play(s)".format(
|
||||
assert plays_number == len(plays), "The graph '{}' should contains {} play(s) but we found {} play(s)".format(
|
||||
playbook_path, plays_number, len(plays))
|
||||
assert tasks_number == len(tasks), "The playbook '{}' should contains {} tasks(s) we found {} tasks".format(
|
||||
playbook_path, tasks_number, len(tasks))
|
||||
assert post_tasks_number == len(post_tasks), "The '{}' playbook should contains {} post tasks(s) we found {} " \
|
||||
"post tasks".format(playbook_path, post_tasks_number, len(post_tasks))
|
||||
assert pre_tasks_number == len(pre_tasks), "The playbook '{}' should contains {} pre tasks(s) but we found {} " \
|
||||
assert pre_tasks_number == len(pre_tasks), "The graph '{}' should contains {} pre tasks(s) but we found {} " \
|
||||
"pre tasks".format(playbook_path, pre_tasks_number, len(pre_tasks))
|
||||
|
||||
assert roles_number == len(roles), "The playbook '{}' should contains {} role(s) but we found {} role(s)".format(
|
||||
playbook_path, roles_number, len(roles))
|
||||
assert tasks_number == len(tasks), "The graph '{}' should contains {} tasks(s) but we found {} tasks".format(
|
||||
playbook_path, tasks_number, len(tasks))
|
||||
assert post_tasks_number == len(post_tasks), "The graph '{}' should contains {} post tasks(s) but we found {} " \
|
||||
"post tasks".format(playbook_path, post_tasks_number, len(post_tasks))
|
||||
|
||||
assert blocks_number == len(blocks), "The graph '{}' should contains {} blocks(s) but we found {} " \
|
||||
"pre tasks".format(playbook_path, blocks_number, len(blocks))
|
||||
|
||||
return {'tasks': tasks, 'plays': plays, 'post_tasks': post_tasks, 'pre_tasks': pre_tasks, "roles": roles}
|
||||
|
||||
|
@ -160,7 +163,8 @@ def test_with_block(request):
|
|||
"""
|
||||
svg_path, playbook_path = run_grapher("with_block.yml", output_filename=request.node.name)
|
||||
|
||||
_common_tests(svg_path=svg_path, playbook_path=playbook_path, plays_number=1, tasks_number=3)
|
||||
_common_tests(svg_path=svg_path, playbook_path=playbook_path, plays_number=1, tasks_number=6, post_tasks_number=1,
|
||||
blocks_number=2)
|
||||
|
||||
|
||||
def test_nested_include_tasks(request):
|
||||
|
|
Loading…
Reference in a new issue