From f9f666ebe2ba183d255c22f1e8d71c103aa55878 Mon Sep 17 00:00:00 2001 From: Shatakshi Mishra Date: Wed, 20 Nov 2024 10:30:57 +0530 Subject: [PATCH] Scaffold filter plugin through add subcommand (#332) * Scaffold filter plugin through add subcommand * Add tests * Differentiate plugin output based on plugin type * cleanup in the plugin template * minor test fix --- src/ansible_creator/arg_parser.py | 2 +- .../plugins/filter/hello_world.py.j2 | 21 +++-- src/ansible_creator/subcommands/add.py | 10 +-- .../testcol/plugins/filter/hello_world.py | 10 ++- tests/units/test_add.py | 83 ++++++++++++++++++- 5 files changed, 105 insertions(+), 21 deletions(-) diff --git a/src/ansible_creator/arg_parser.py b/src/ansible_creator/arg_parser.py index 5e65ee91..18fc1572 100644 --- a/src/ansible_creator/arg_parser.py +++ b/src/ansible_creator/arg_parser.py @@ -38,7 +38,6 @@ "add resource devcontainer", "add resource role", "add plugin action", - "add plugin filter", ) @@ -351,6 +350,7 @@ def _add_plugin_filter(self, subparser: SubParser[ArgumentParser]) -> None: formatter_class=CustomHelpFormatter, ) self._add_args_common(parser) + self._add_overwrite(parser) self._add_args_plugin_common(parser) def _add_plugin_lookup(self, subparser: SubParser[ArgumentParser]) -> None: diff --git a/src/ansible_creator/resources/collection_project/plugins/filter/hello_world.py.j2 b/src/ansible_creator/resources/collection_project/plugins/filter/hello_world.py.j2 index 04753508..7560113d 100644 --- a/src/ansible_creator/resources/collection_project/plugins/filter/hello_world.py.j2 +++ b/src/ansible_creator/resources/collection_project/plugins/filter/hello_world.py.j2 @@ -1,4 +1,11 @@ -"""A hello-world filter plugin in {{ namespace }}.{{ collection_name }}.""" +{# filter_plugin_template.j2 #} +{%- set filter_name = plugin_name | default("hello_world") -%} +{%- set author = author | default("Your Name") -%} +{%- set description = description | default("A custom filter plugin for Ansible.") -%} +{%- set license = license | default("GPL-3.0-or-later") -%} +# {{ filter_name }}.py - {{ description }} +# Author: {{ author }} +# License: {{ license }} from __future__ import absolute_import, annotations, division, print_function @@ -13,10 +20,10 @@ if TYPE_CHECKING: DOCUMENTATION = """ - name: hello_world - author: {{ namespace | capitalize }} {{ collection_name | capitalize }} + name: {{ filter_name }} + author: {{ author }} version_added: "1.0.0" - short_description: Demo filter plugin that returns a Hello message. + short_description: {{ description }} description: - This is a demo filter plugin designed to return Hello message. options: @@ -26,11 +33,11 @@ DOCUMENTATION = """ """ EXAMPLES = """ -# hello_world filter example +# {{ filter_name }} filter example {% raw %} - name: Display a hello message ansible.builtin.debug: - msg: "{{ 'ansible-creator' {%- endraw %} | {{ namespace }}.{{ collection_name }}.hello_world }}" + msg: "{{ 'ansible-creator' {%- endraw %} | {{ filter_name }} }}" """ @@ -55,4 +62,4 @@ class FilterModule: Returns: dict: The filter plugin functions. """ - return {"hello_world": _hello_world} + return {"{{ filter_name }}": _hello_world} diff --git a/src/ansible_creator/subcommands/add.py b/src/ansible_creator/subcommands/add.py index 94f6a051..4647d8e6 100644 --- a/src/ansible_creator/subcommands/add.py +++ b/src/ansible_creator/subcommands/add.py @@ -170,8 +170,8 @@ def _plugin_scaffold(self, plugin_path: Path) -> None: self.output.debug(f"Started copying {self._project} plugin to destination") # Call the appropriate scaffolding function based on the plugin type - if self._plugin_type == "lookup": - template_data = self._get_lookup_plugin_template_data() + if self._plugin_type in ("lookup", "filter"): + template_data = self._get_plugin_template_data() else: msg = f"Unsupported plugin type: {self._plugin_type}" @@ -212,7 +212,7 @@ def _perform_plugin_scaffold(self, template_data: TemplateData, plugin_path: Pat if not paths.has_conflicts() or self._force or self._overwrite: copier.copy_containers(paths) - self.output.note(f"Plugin added to {plugin_path}") + self.output.note(f"{self._plugin_type.capitalize()} plugin added to {plugin_path}") return if not self._overwrite: @@ -228,7 +228,7 @@ def _perform_plugin_scaffold(self, template_data: TemplateData, plugin_path: Pat ) raise CreatorError(msg) - self.output.note(f"Plugin added to {plugin_path}") + self.output.note(f"{self._plugin_type.capitalize()} plugin added to {plugin_path}") def _get_devfile_template_data(self) -> TemplateData: """Get the template data for devfile resources. @@ -242,7 +242,7 @@ def _get_devfile_template_data(self) -> TemplateData: dev_file_name=self.unique_name_in_devfile(), ) - def _get_lookup_plugin_template_data(self) -> TemplateData: + def _get_plugin_template_data(self) -> TemplateData: """Get the template data for lookup plugin. Returns: diff --git a/tests/fixtures/collection/testorg/testcol/plugins/filter/hello_world.py b/tests/fixtures/collection/testorg/testcol/plugins/filter/hello_world.py index 1733e637..8924fcf1 100644 --- a/tests/fixtures/collection/testorg/testcol/plugins/filter/hello_world.py +++ b/tests/fixtures/collection/testorg/testcol/plugins/filter/hello_world.py @@ -1,4 +1,6 @@ -"""A hello-world filter plugin in testorg.testcol.""" +# hello_world.py - A custom filter plugin for Ansible. +# Author: Your Name +# License: GPL-3.0-or-later from __future__ import absolute_import, annotations, division, print_function @@ -14,9 +16,9 @@ DOCUMENTATION = """ name: hello_world - author: Testorg Testcol + author: Your Name version_added: "1.0.0" - short_description: Demo filter plugin that returns a Hello message. + short_description: A custom filter plugin for Ansible. description: - This is a demo filter plugin designed to return Hello message. options: @@ -30,7 +32,7 @@ - name: Display a hello message ansible.builtin.debug: - msg: "{{ 'ansible-creator' | testorg.testcol.hello_world }}" + msg: "{{ 'ansible-creator' | hello_world }}" """ diff --git a/tests/units/test_add.py b/tests/units/test_add.py index 8a06bda9..9fe2eacd 100644 --- a/tests/units/test_add.py +++ b/tests/units/test_add.py @@ -303,6 +303,82 @@ def test_run_error_unsupported_resource_type( assert "Unsupported resource type: unsupported_type" in str(exc_info.value) +def test_run_success_add_plugin_filter( + capsys: pytest.CaptureFixture[str], + tmp_path: Path, + cli_args: ConfigDict, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test Add.run(). + + Successfully add plugin to path + Args: + capsys: Pytest fixture to capture stdout and stderr. + tmp_path: Temporary directory path. + cli_args: Dictionary, partial Add class object. + monkeypatch: Pytest monkeypatch fixture. + """ + cli_args["plugin_type"] = "filter" + add = Add( + Config(**cli_args), + ) + + # Mock the "_check_collection_path" method + def mock_check_collection_path() -> None: + """Mock function to skip checking collection path.""" + + monkeypatch.setattr( + Add, + "_check_collection_path", + staticmethod(mock_check_collection_path), + ) + add.run() + result = capsys.readouterr().out + assert re.search("Note: Filter plugin added to", result) is not None + + expected_file = tmp_path / "plugins" / "filter" / "hello_world.py" + effective_file = ( + FIXTURES_DIR + / "collection" + / "testorg" + / "testcol" + / "plugins" + / "filter" + / "hello_world.py" + ) + cmp_result = cmp(expected_file, effective_file, shallow=False) + assert cmp_result + + conflict_file = tmp_path / "plugins" / "filter" / "hello_world.py" + conflict_file.write_text("Author: Your Name") + + # expect a CreatorError when the response to overwrite is no. + monkeypatch.setattr("builtins.input", lambda _: "n") + fail_msg = ( + "The destination directory contains files that will be overwritten." + " Please re-run ansible-creator with --overwrite to continue." + ) + with pytest.raises( + CreatorError, + match=fail_msg, + ): + add.run() + + # expect a warning followed by playbook project creation msg + # when response to overwrite is yes. + monkeypatch.setattr("builtins.input", lambda _: "y") + add.run() + result = capsys.readouterr().out + assert ( + re.search( + "already exists", + result, + ) + is not None + ), result + assert re.search("Note: Filter plugin added to", result) is not None + + def test_run_success_add_plugin_lookup( capsys: pytest.CaptureFixture[str], tmp_path: Path, @@ -335,7 +411,7 @@ def mock_check_collection_path() -> None: ) add.run() result = capsys.readouterr().out - assert re.search("Note: Plugin added to", result) is not None + assert re.search("Note: Lookup plugin added to", result) is not None expected_file = tmp_path / "plugins" / "lookup" / "hello_world.py" effective_file = ( @@ -377,7 +453,7 @@ def mock_check_collection_path() -> None: ) is not None ), result - assert re.search("Note: Plugin added to", result) is not None + assert re.search("Note: Lookup plugin added to", result) is not None def test_run_error_plugin_no_overwrite( @@ -412,7 +488,7 @@ def mock_check_collection_path() -> None: ) add.run() result = capsys.readouterr().out - assert re.search("Note: Plugin added to", result) is not None + assert re.search("Note: Lookup plugin added to", result) is not None expected_file = tmp_path / "plugins" / "lookup" / "hello_world.py" effective_file = ( @@ -452,7 +528,6 @@ def test_run_error_unsupported_plugin_type( cli_args: Dictionary, partial Add class object. monkeypatch: Pytest monkeypatch fixture. """ - cli_args["plugin_type"] = "lookup" add = Add( Config(**cli_args), )