Skip to content

Commit

Permalink
add a new flag to show the handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
haidaraM committed Dec 22, 2024
1 parent da08032 commit 5e83180
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 92 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ JavaScript:
Lastly, you can provide your own protocol formats
with `--open-protocol-handler custom --open-protocol-custom-formats '{}'`. See the help
and [an example.](https://github.com/haidaraM/ansible-playbook-grapher/blob/34e0aef74b82808dceb6ccfbeb333c0b531eac12/ansibleplaybookgrapher/renderer/__init__.py#L32-L41)
To not confuse with [Ansible Handlers.](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html)
- Export the dot file used to generate the graph with Graphviz.

## Prerequisites
Expand Down Expand Up @@ -397,11 +398,14 @@ regarding the blocks.
The available options:

```
usage: ansible-playbook-grapher [-h] [-v] [-i INVENTORY] [--include-role-tasks] [-s] [--view] [-o OUTPUT_FILENAME]
[--open-protocol-handler {default,vscode,custom}] [--open-protocol-custom-formats OPEN_PROTOCOL_CUSTOM_FORMATS]
[--group-roles-by-name] [--renderer {graphviz,mermaid-flowchart,json}] [--renderer-mermaid-directive RENDERER_MERMAID_DIRECTIVE]
[--renderer-mermaid-orientation {TD,RL,BT,RL,LR}] [--version] [--hide-plays-without-roles] [--hide-empty-plays] [-t TAGS]
[--skip-tags SKIP_TAGS] [--vault-id VAULT_IDS] [-J | --vault-password-file VAULT_PASSWORD_FILES] [-e EXTRA_VARS]
usage: ansible-playbook-grapher [-h] [-v] [--exclude-roles EXCLUDE_ROLES] [--only-roles] [-i INVENTORY] [--include-role-tasks] [-s]
[--view] [-o OUTPUT_FILENAME] [--open-protocol-handler {default,vscode,custom}]
[--open-protocol-custom-formats OPEN_PROTOCOL_CUSTOM_FORMATS] [--group-roles-by-name]
[--renderer {graphviz,mermaid-flowchart,json}]
[--renderer-mermaid-directive RENDERER_MERMAID_DIRECTIVE]
[--renderer-mermaid-orientation {TD,RL,BT,RL,LR}] [--version] [--hide-plays-without-roles]
[--hide-empty-plays] [--show-handlers] [-t TAGS] [--skip-tags SKIP_TAGS] [--vault-id VAULT_IDS] [-J |
--vault-password-file VAULT_PASSWORD_FILES] [-e EXTRA_VARS]
playbooks [playbooks ...]
Make graphs from your Ansible Playbooks.
Expand All @@ -419,7 +423,7 @@ options:
Hide the plays that end up with no roles in the graph (after applying the tags filter). Only roles at the play level and include_role as tasks are
considered (no import_role).
--include-role-tasks Include the tasks of the roles in the graph. Applied when parsing the playbooks.
--only-roles Ignore all tasks when rendering graphs.
--only-roles Only display the roles in the graph (ignoring the tasks)
--open-protocol-custom-formats OPEN_PROTOCOL_CUSTOM_FORMATS
The custom formats to use as URLs for the nodes in the graph. Required if --open-protocol-handler is set to custom. You should provide a
JSON formatted string like: {"file": "", "folder": ""}. Example: If you want to open folders (roles) inside the browser and files
Expand All @@ -438,12 +442,13 @@ options:
https://mermaid.js.org/config/directives.html. Default: '%%{ init: { "flowchart": { "curve": "bumpX" } } }%%'
--renderer-mermaid-orientation {TD,RL,BT,RL,LR}
The orientation of the flow chart. Default: 'LR'
--show-handlers Show the handlers in the graph. See the limitations in the project README on GitHub.
--skip-tags SKIP_TAGS
only run plays and tasks whose tags do not match these values. This argument may be specified multiple times.
--vault-id VAULT_IDS the vault identity to use. This argument may be specified multiple times.
--vault-password-file VAULT_PASSWORD_FILES, --vault-pass-file VAULT_PASSWORD_FILES
vault password file
--version
--version show program's version number and exit
--view Automatically open the resulting SVG file with your system's default viewer application for the file type
-J, --ask-vault-password, --ask-vault-pass
ask for vault password
Expand Down Expand Up @@ -481,6 +486,9 @@ More information [here](https://docs.ansible.com/ansible/latest/reference_append
Always check the edge label to know the task order.
- The label of the edges may overlap with each other. They are positioned so that they are as close as possible to
the target nodes. If the same role is used in multiple plays or playbooks, the labels can overlap.
- **Ansible Handlers**: The handlers are partially supported for the moment. Their position in the graph doesn't entirely
reflect their real order of execution in the playbook. They are displayed at the end of the play and roles, but they
might be executed before that.

## Contribution

Expand Down
11 changes: 11 additions & 0 deletions ansibleplaybookgrapher/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def run(self):
save_dot_file=self.options.save_dot_file,
hide_empty_plays=self.options.hide_empty_plays,
hide_plays_without_roles=self.options.hide_plays_without_roles,
show_handlers=self.options.show_handlers,
)

case "mermaid-flowchart":
Expand All @@ -113,6 +114,7 @@ def run(self):
orientation=self.options.renderer_mermaid_orientation,
hide_empty_plays=self.options.hide_empty_plays,
hide_plays_without_roles=self.options.hide_plays_without_roles,
show_handlers=self.options.show_handlers,
)

case "json":
Expand All @@ -124,6 +126,7 @@ def run(self):
view=self.options.view,
hide_empty_plays=self.options.hide_empty_plays,
hide_plays_without_roles=self.options.hide_plays_without_roles,
show_handlers=self.options.show_handlers,
)

case _:
Expand Down Expand Up @@ -303,6 +306,14 @@ def _add_my_options(self) -> None:
help="Hide the plays that end up with no tasks in the graph (after applying the tags filter).",
)

self.parser.add_argument(
"--show-handlers",
dest="show_handlers",
action="store_true",
default=False,
help="Show the handlers in the graph. See the limitations in the project README on GitHub.",
)

self.parser.add_argument(
"playbooks",
help="Playbook(s) to graph. You can specify multiple playbooks, separated by spaces and reference playbooks in collections.",
Expand Down
51 changes: 31 additions & 20 deletions ansibleplaybookgrapher/renderer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,35 @@ def build_playbook(
self,
hide_empty_plays: bool = False,
hide_plays_without_roles: bool = False,
show_handlers: bool = False,
**kwargs,
) -> str:
"""Build the whole playbook
:param hide_empty_plays: Whether to hide empty plays or not
:param hide_plays_without_roles:
:param hide_plays_without_roles: Whether to hide plays without roles or not
:param show_handlers: Whether to show the handlers or not.
:param kwargs:
:return: The rendered playbook as a string.
"""

@abstractmethod
def build_play(self, play_node: PlayNode, **kwargs) -> None:
def build_play(
self, play_node: PlayNode, show_handlers: bool = False, **kwargs
) -> None:
"""Build a single play to be rendered
:param play_node:
:param play_node: The play to render
:param show_handlers: Whether to show the handlers or not.
:param kwargs:
:return:
"""

def traverse_play(self, play_node: PlayNode, **kwargs) -> None:
def traverse_play(
self, play_node: PlayNode, show_handlers: bool = False, **kwargs
) -> None:
"""Traverse a play to build the graph: pre_tasks, roles, tasks, post_tasks
:param play_node:
:param show_handlers: Whether to show the handlers or not.
:param kwargs:
:return:
"""
Expand All @@ -178,13 +187,14 @@ def traverse_play(self, play_node: PlayNode, **kwargs) -> None:
**kwargs,
)

for r_handler in role.handlers:
self.build_node(
node=r_handler,
color=color,
fontcolor=play_font_color,
**kwargs,
)
if show_handlers:
for r_handler in role.handlers:
self.build_node(
node=r_handler,
color=color,
fontcolor=play_font_color,
**kwargs,
)

# tasks
for task in play_node.tasks:
Expand All @@ -204,15 +214,16 @@ def traverse_play(self, play_node: PlayNode, **kwargs) -> None:
**kwargs,
)

# play handlers
for p_handler in play_node.handlers:
self.build_node(
node=p_handler,
color=color,
fontcolor=play_font_color,
node_label_prefix="[handler] ",
**kwargs,
)
if show_handlers:
# play handlers
for p_handler in play_node.handlers:
self.build_node(
node=p_handler,
color=color,
fontcolor=play_font_color,
node_label_prefix="[handler] ",
**kwargs,
)

@abstractmethod
def build_task(
Expand Down
29 changes: 20 additions & 9 deletions ansibleplaybookgrapher/renderer/graphviz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ def render(
view: bool = False,
hide_empty_plays: bool = False,
hide_plays_without_roles: bool = False,
show_handlers: bool = False,
**kwargs,
) -> str:
""":param open_protocol_handler: The protocol handler name to use
"""
:param open_protocol_handler: The protocol handler name to use
:param open_protocol_custom_formats: The custom formats to use when the protocol handler is set to custom
:param output_filename: The output filename without any extension
:param view: Whether to open the rendered file in the default viewer
:param hide_empty_plays: Whether to hide empty plays or not when rendering the graph
:param hide_plays_without_roles: Whether to hide plays without any roles or not
:param show_handlers:
:return: The path of the rendered file
"""
save_dot_file = kwargs.get("save_dot_file", False)
Expand All @@ -85,6 +88,7 @@ def render(
builder.build_playbook(
hide_empty_plays=hide_empty_plays,
hide_plays_without_roles=hide_plays_without_roles,
show_handlers=show_handlers
)
roles_built.update(builder.roles_built)

Expand Down Expand Up @@ -244,6 +248,7 @@ def build_role(
**kwargs,
) -> None:
"""Render a role in the graph
:return:
"""
digraph = kwargs["digraph"]
Expand All @@ -267,10 +272,7 @@ def build_role(

self.roles_built.add(role_node)

if role_node.include_role: # For include_role, we point to a file
url = self.get_node_url(role_node)
else: # For normal role invocation, we point to the folder
url = self.get_node_url(role_node)
url = self.get_node_url(role_node)

plays_using_this_role = self.roles_usage[role_node]
if len(plays_using_this_role) > 1:
Expand Down Expand Up @@ -304,11 +306,13 @@ def build_playbook(
self,
hide_empty_plays: bool = False,
hide_plays_without_roles: bool = False,
show_handlers: bool = False,
**kwargs,
) -> str:
"""Convert the PlaybookNode to the graphviz dot format
:param hide_empty_plays: Whether to hide empty plays or not when rendering the graph
:param hide_plays_without_roles: Whether to hide plays without any roles or not
:param show_handlers: Whether to show the handlers or not.
:return: The text representation of the graphviz dot format for the playbook.
"""
display.vvv("Converting the graph to the dot format for graphviz")
Expand All @@ -326,12 +330,19 @@ def build_playbook(
exclude_without_roles=hide_plays_without_roles,
):
with self.digraph.subgraph(name=play.name) as play_subgraph:
self.build_play(play, digraph=play_subgraph, **kwargs)
self.build_play(
play, digraph=play_subgraph, show_handlers=show_handlers, **kwargs
)

return self.digraph.source

def build_play(self, play_node: PlayNode, **kwargs) -> None:
""":param play_node:
def build_play(
self, play_node: PlayNode, show_handlers: bool = False, **kwargs
) -> None:
"""
:param show_handlers:
:param play_node:
:param kwargs:
:return:
"""
Expand Down Expand Up @@ -369,4 +380,4 @@ def build_play(self, play_node: PlayNode, **kwargs) -> None:
)

# traverse the play
self.traverse_play(play_node, **kwargs)
self.traverse_play(play_node, show_handlers=show_handlers, **kwargs)
6 changes: 5 additions & 1 deletion ansibleplaybookgrapher/renderer/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def render(
view: bool = False,
hide_empty_plays: bool = False,
hide_plays_without_roles: bool = False,
show_handlers: bool = False,
**kwargs,
) -> str:
playbooks = []
Expand Down Expand Up @@ -110,9 +111,12 @@ def build_playbook(

return json.dumps(self.json_output)

def build_play(self, play_node: PlayNode, **kwargs) -> None:
def build_play(
self, play_node: PlayNode, show_handlers: bool = False, **kwargs
) -> None:
"""Not needed.
:param show_handlers:
:param play_node:
:param kwargs:
:return:
Expand Down
14 changes: 11 additions & 3 deletions ansibleplaybookgrapher/renderer/mermaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def render(
view: bool = False,
hide_empty_plays: bool = False,
hide_plays_without_roles: bool = False,
show_handlers: bool = False,
directive: str = DEFAULT_DIRECTIVE,
orientation: str = DEFAULT_ORIENTATION,
**kwargs,
Expand All @@ -59,6 +60,7 @@ def render(
:param view: Not supported for the moment.
:param hide_empty_plays: Whether to hide empty plays or not when rendering the graph.
:param hide_plays_without_roles: Whether to hide plays without any roles or not.
:param show_handlers: Whether to show handlers or not.
:param directive: Mermaid directive.
:param orientation: Mermaid graph orientation.
:param kwargs:
Expand Down Expand Up @@ -95,6 +97,7 @@ def render(
mermaid_code += playbook_builder.build_playbook(
hide_empty_plays=hide_empty_plays,
hide_plays_without_roles=hide_plays_without_roles,
show_handlers=show_handlers
)
link_order = playbook_builder.link_order
roles_built.update(playbook_builder.roles_built)
Expand Down Expand Up @@ -173,13 +176,15 @@ def build_playbook(
self,
hide_empty_plays: bool = False,
hide_plays_without_roles: bool = False,
show_handlers: bool = False,
**kwargs,
) -> str:
"""Build a playbook.
:param hide_plays_without_roles: Whether to hide plays without any roles or not
:param hide_empty_plays: Whether to hide empty plays or not
:param hide_plays_without_roles: Whether to hide plays without any roles or not
:param show_handlers: Whether to show handlers or not
:param kwargs:
:return:
"""
Expand All @@ -200,16 +205,19 @@ def build_playbook(
exclude_empty=hide_empty_plays,
exclude_without_roles=hide_plays_without_roles,
):
self.build_play(play_node)
self.build_play(play_node, show_handlers=show_handlers, **kwargs)
self._indentation_level -= 1

self.add_comment(f"End of the playbook '{self.playbook_node.display_name()}'\n")

return self.mermaid_code

def build_play(self, play_node: PlayNode, **kwargs) -> None:
def build_play(
self, play_node: PlayNode, show_handlers: bool = False, **kwargs
) -> None:
"""Build a play.
:param show_handlers:
:param play_node:
:param kwargs:
:return:
Expand All @@ -235,7 +243,7 @@ def build_play(self, play_node: PlayNode, **kwargs) -> None:

# traverse the play
self._indentation_level += 1
self.traverse_play(play_node)
self.traverse_play(play_node, show_handlers, **kwargs)
self._indentation_level -= 1

self.add_comment(f"End of the play '{play_node.display_name()}'")
Expand Down
Loading

0 comments on commit 5e83180

Please sign in to comment.