Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ansibleplaybookgrapher/graph_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ class HandlerNode(TaskNode):
Key things to note:

- Each handler should have a globally unique name. If multiple handlers are defined with the same name, only the last
one loaded into the play can be notified and executed, effectively shadowing all of the previous handlers with the same name.
one loaded into the play can be notified and executed, effectively shadowing all the previous handlers with the same name.
- There is only one global scope for handlers (handler names and listen topics) regardless of where the handlers are
defined. This also includes handlers defined in roles.
- If a handler is defined in a role, it can be notified using the role name as a prefix. Example: notify: "role_name : handler_name"
Expand Down
8 changes: 6 additions & 2 deletions ansibleplaybookgrapher/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from abc import ABC, abstractmethod
from types import NoneType

from ansible.cli import CLI
from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable
Expand Down Expand Up @@ -243,7 +242,7 @@ def parse(self, *args, **kwargs) -> PlaybookNode:
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()}")
display.vvvv(f"Loader basedir set to {self.data_loader.get_basedir()}")

play_vars = self.variable_manager.get_vars(play)
play_hosts = [
Expand Down Expand Up @@ -390,6 +389,7 @@ def _include_tasks_in_blocks(
:param node_type: The type of the node. It can be a task, a pre_task, a post_task or a handler.
:return:
"""
block_node = None
if Block.is_block(block.get_ds()):
# Here we have an explicit block. Ansible internally converts all normal tasks to Block
block_node = BlockNode(
Expand Down Expand Up @@ -567,3 +567,7 @@ def _include_tasks_in_blocks(
node_type=node_type,
parent_node=parent_nodes[-1],
)

if block_node:
# We remove the block node from the parent nodes given we are done processing it
parent_nodes.pop()
10 changes: 10 additions & 0 deletions tests/fixtures/include_with_blocks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: blocker
hosts: localhost
gather_facts: no
tasks:
- name: including block tasks - block 1
block:
- name: include
include_tasks: tasks/consecutive_block_tasks.yml

13 changes: 13 additions & 0 deletions tests/fixtures/tasks/consecutive_block_tasks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- name: block2
block:
- name: b2 - task1
debug:
msg: task1
- name: block3
block:
- name: b3 - task1
debug:
msg: task1
- name: b3 - task2
debug:
msg: task2
34 changes: 34 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,3 +695,37 @@ def test_parsing_tasks_with_tags_in_roles(grapher_cli: PlaybookGrapherCLI) -> No
assert len(play_node.roles) == 1, "Only one role should be in the play"
role = play_node.roles[0]
assert len(role.tasks) == 1, "Only the tagged task should be included in the graph"


@pytest.mark.parametrize("grapher_cli", [["include_with_blocks.yml"]], indirect=True)
def test_parsing_blocks_inside_include_tasks(grapher_cli: PlaybookGrapherCLI) -> None:
"""Test a case where consecutive blocks are defined inside an included task.

:param grapher_cli:
:return:
"""
parser = PlaybookParser(
grapher_cli.options.playbooks[0],
)
playbook_node = parser.parse()

assert len(playbook_node.plays) == 1
assert len(playbook_node.plays[0].tasks) == 1
block_1 = playbook_node.plays[0].tasks[0]
assert isinstance(block_1, BlockNode)
assert len(block_1.tasks) == 2, (
"The outer block should have 2 tasks from the include_task"
)

block_2 = block_1.tasks[0]
assert block_2.name == "block2"
assert isinstance(block_2, BlockNode)
assert len(block_2.tasks) == 1
assert block_2.tasks[0].name == "b2 - task1"

block_3 = block_1.tasks[1]
assert block_3.name == "block3"
assert isinstance(block_3, BlockNode)
assert len(block_3.tasks) == 2
assert block_3.tasks[0].name == "b3 - task1"
assert block_3.tasks[1].name == "b3 - task2"