Skip to content

Commit

Permalink
update get handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
haidaraM committed Jan 9, 2025
1 parent 3679050 commit 4695260
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 72 deletions.
52 changes: 29 additions & 23 deletions ansibleplaybookgrapher/graph_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,34 +580,23 @@ def handlers(self) -> list["HandlerNode"]:
"""
return self.get_nodes("handlers")

def get_handlers(self, handler_name: str) -> list["HandlerNode"]:
"""Return the handlers with the given name if they exist, None otherwise. If multiple handlers have the same name,
only the last one loaded into the play is returned.
def get_handlers(self, notify: list[str]) -> list["HandlerNode"]:
"""Return the handlers notified by the given names if they exist
You must calculate the indices before calling this method.
- The name can also be prefixed with the role name if the handler is defined in a role.
Example: "role_name : handler_name".
- You can also pass the listen topic of the handler. Example: "handler_name : listen_topic"
:param handler_name: The name of the handler to get.
:param notify: The name of the handler to get.
:return:
"""
# TODO: add a warning when a notified handler is not found
result = []
for h in reversed(self.handlers):
if h in result:
continue

def matches_handler(handler: "HandlerNode", name: str) -> bool:
"""Check if the handler matches the given name."""
if handler.name == name or name in handler.listen:
return True
if role_node := handler.get_first_parent_matching_type(RoleNode):
name_candidate = f"{role_node.name} : {name}"
return (
handler.name == name_candidate or name_candidate in handler.listen
)
return False

result = set()
for h in self.handlers:
if matches_handler(h, handler_name):
result.add(h)
for n in notify:
if h.matches_handler(n):
result.append(h)

return sorted(result, key=lambda x: x.index)

Expand Down Expand Up @@ -702,7 +691,7 @@ def __init__(
parent=parent,
)
# The list of handlers to notify
self.notify = notify or []
self.notify: list[str] = notify or []

def to_dict(self, **kwargs) -> dict:
"""Return a dictionary representation of this node. This representation is not meant to get the original object
Expand Down Expand Up @@ -790,6 +779,23 @@ def display_name(self) -> str:
"""
return f"[handler] {self.name}"

def matches_handler(self, name: str) -> bool:
"""Check if the handler matches the given name.
- The name can also be prefixed with the role name if the handler is defined in a role.
Example: "role_name : handler_name".
- You can also pass the listen topic of the handler. Example: "handler_name : listen_topic"
:param name: The name of the handler to check (from the notify attribute)
"""
if self.name == name or name in self.listen:
return True

if role_node := self.get_first_parent_matching_type(RoleNode):
name_candidate = f"{role_node.name} : {name}"
return self.name == name_candidate or name_candidate in self.listen

return False


class RoleNode(LoopMixin, CompositeNode):
"""A role node. A role is a composition of tasks."""
Expand Down
1 change: 1 addition & 0 deletions ansibleplaybookgrapher/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ def parse(self, *args, **kwargs) -> PlaybookNode:
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")
display.v(f"{len(play_node.handlers)} handlers(s) added to the play")
# moving to the next play

playbook_root_node.calculate_indices()
Expand Down
1 change: 0 additions & 1 deletion ansibleplaybookgrapher/renderer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ def traverse_play(self, play_node: PlayNode, **kwargs) -> None:
node=p_handler,
color=color,
fontcolor=play_font_color,
node_label_prefix="[handler] ",
**kwargs,
)

Expand Down
33 changes: 13 additions & 20 deletions ansibleplaybookgrapher/renderer/graphviz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,27 +206,20 @@ def build_task(

# Build the edge from the task to the handlers it notifies
if self.show_handlers:
counter = 1
for notify_name in task_node.notify:
handlers = play_node.get_handlers(notify_name)
for handler in handlers:
digraph.edge(
task_node.id,
handler.id,
color=color,
fontcolor=color,
id=f"edge_{task_node.index}_{task_node.id}_{handler.id}",
style="dotted",
label=f"{counter}",
tooltip=handler.name,
labeltooltip=handler.name,
)
counter += 1
notified_handlers = play_node.get_handlers(task_node.notify)

if not handlers:
display.warning(
f"The handler '{notify_name}' not found in the play '{play_node.name}'",
)
for counter, handler in enumerate(notified_handlers, 1):
digraph.edge(
task_node.id,
handler.id,
color=color,
fontcolor=color,
id=f"edge_{task_node.index}_{task_node.id}_{handler.id}",
style="dotted",
label=f"{counter}",
tooltip=handler.name,
labeltooltip=handler.name,
)

def build_block(
self,
Expand Down
33 changes: 12 additions & 21 deletions ansibleplaybookgrapher/renderer/mermaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,35 +278,26 @@ def build_task(
style=style,
)

# From parent to task
# From the parent to the task
self.add_link(
source_id=task_node.parent.id,
text=link_text,
dest_id=task_node.id,
text=link_text,
style=f"stroke:{color},color:{color}",
link_type=link_type,
)

if self.show_handlers:
# From task to handler
counter = 1
for notify_name in task_node.notify:
handlers = play_node.get_handlers(notify_name)

for handler in handlers:
self.add_link(
source_id=task_node.id,
text=f"{counter}",
dest_id=handler.id,
style=f"stroke:{color},color:{color}",
link_type="-.->",
)
counter += 1

if not handlers:
display.warning(
f"The handler '{notify_name}' not found in the play '{play_node.name}'",
)
notified_handlers = play_node.get_handlers(task_node.notify)

for counter, handler in enumerate(notified_handlers, 1):
self.add_link(
source_id=task_node.id,
dest_id=handler.id,
text=f"{counter}",
style=f"stroke:{color},color:{color}",
link_type="-.->",
)

def add_node(self, node_id: str, shape: str, label: str, style: str = "") -> None:
"""Add a node to the mermaid code.
Expand Down
15 changes: 8 additions & 7 deletions tests/test_graph_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,13 @@ def test_get_handlers_from_play():

play.calculate_indices()

assert play.get_handlers("fake handler") == []
assert play.get_handlers("restart nginx") == [restart_nginx]
assert play.get_handlers("restart postgres") == [restart_postgres]
assert play.get_handlers("restart mysql") == [
restart_mysql_2
], "The last mysql handler should be returned"
assert play.get_handlers(["fake handler"]) == []
assert play.get_handlers(["restart nginx"]) == [restart_nginx]
assert play.get_handlers(["restart postgres"]) == [restart_postgres]

mysql_handlers = play.get_handlers(["restart mysql"])
assert len(mysql_handlers) == 1
assert mysql_handlers[0].id == restart_mysql_2.id

# When multiple handlers have the same listen, we return them in the order they are defined
assert play.get_handlers("restart dbs") == [restart_postgres, restart_mysql_2]
assert play.get_handlers(["restart dbs"]) == [restart_postgres, restart_mysql_2]

0 comments on commit 4695260

Please sign in to comment.