Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffold lookup plugin through add subcommand #327

Merged
merged 16 commits into from
Nov 19, 2024
2 changes: 1 addition & 1 deletion src/ansible_creator/arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"add resource role",
"add plugin action",
"add plugin filter",
"add plugin lookup",
)


Expand Down Expand Up @@ -366,6 +365,7 @@ def _add_plugin_lookup(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_overwrite(self, parser: ArgumentParser) -> None:
Expand Down
6 changes: 5 additions & 1 deletion src/ansible_creator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class Config:
collection_name: The name of the collection.
namespace: The namespace for the collection.
resource_type: The type of resource to be scaffolded.
plugin_name: The name of plugin to be scaffolded.
plugin_type: The type of plugin to be scaffolded.
type: The type of the project for which the resource is being scaffolded.
path: The file path where the resource should be added.
"""
Expand All @@ -47,8 +49,10 @@ class Config:
collection_name: str | None = None
namespace: str = ""
resource_type: str = ""
plugin_name: str = ""
plugin_type: str = ""
type: str = ""
path: str = ""
path: str | Path = "./"

def __post_init__(self) -> None:
"""Post process config values."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{# lookup_plugin_template.j2 #}
{%- set lookup_name = plugin_name | default("hello_world") -%}
{%- set author = author | default("Your Name") -%}
{%- set description = description | default("A custom lookup plugin for Ansible.") -%}
{%- set license = license | default("GPL-3.0-or-later") -%}
# pylint: disable=E0401
# {{ lookup_name }}.py - {{ description }}
# Author: {{ author }}
# License: {{ license }}

from ansible.plugins.lookup import LookupBase # type: ignore
from ansible.errors import AnsibleError # type: ignore
from ansible.utils.display import Display # type: ignore
from typing import Any, Optional, Dict, List

display = Display()

DOCUMENTATION = """
name: {{ lookup_name }}
author: {{ author }}
version_added: "1.0.0"
short_description: {{ description }}
description:
- This is a custom lookup plugin to provide lookup functionality.
options:
_terms:
description: Terms to lookup
required: True
notes:
- This is a scaffold template. Customize the plugin to fit your needs.
"""

EXAMPLES = """
- name: Example usage of {{ lookup_name }}
{%- raw %}
ansible.builtin.debug:
msg: "{{ lookup('{%- endraw %}{{ lookup_name }}', 'example_term') }}"
"""

RETURN = """
_list:
description: The list of values found by the lookup
type: list
"""


class LookupModule(LookupBase): # type: ignore[misc]
"""
Custom Ansible lookup plugin: hello_world
A custom lookup plugin for Ansible.
"""

def run(
self,
terms: List[str],
variables: Optional[Dict[str, Any]] = None,
**kwargs: Dict[str, Any],
) -> list[str]:
"""
Run the lookup with the specified terms.

Args:
terms: A list of terms to lookup.
variables: Additional variables.
**kwargs: Additional keyword arguments.

Returns:
list: A list of processed results.

Raises:
AnsibleError: If the 'terms' parameter is not a list.
"""
if not isinstance(terms, list):
raise AnsibleError("The 'terms' parameter must be a list.")

display.vvv(f"Running hello_world lookup plugin with terms: {terms}")

try:
# Example processing logic - Replace this with actual lookup code
result = [term.upper() for term in terms]

display.vvv(f"Result from hello_world lookup: {result}")
return result

except Exception as e:
raise AnsibleError(f"Error in hello_world plugin: {e}")
122 changes: 114 additions & 8 deletions src/ansible_creator/subcommands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ def __init__(
config: App configuration object.
"""
self._resource_type: str = config.resource_type
self._plugin_type: str = config.plugin_type
self._resource_id: str = f"common.{self._resource_type}"
self._plugin_id: str = f"collection_project.plugins.{self._plugin_type}"
self._plugin_name: str = config.plugin_name
self._add_path: Path = Path(config.path)
self._force = config.force
self._overwrite = config.overwrite
Expand All @@ -43,12 +46,17 @@ def __init__(

def run(self) -> None:
"""Start scaffolding the resource file."""
self._check_add_path()
self._check_path_exists()
self.output.debug(msg=f"final collection path set to {self._add_path}")

self._scaffold()

def _check_add_path(self) -> None:
if self._resource_type:
self._resource_scaffold()
elif self._plugin_type:
self._check_collection_path()
plugin_path = self._add_path / "plugins" / self._plugin_type
plugin_path.mkdir(parents=True, exist_ok=True)
self._plugin_scaffold(plugin_path)

def _check_path_exists(self) -> None:
"""Validate the provided add path.

Raises:
Expand All @@ -58,6 +66,20 @@ def _check_add_path(self) -> None:
msg = f"The path {self._add_path} does not exist. Please provide an existing directory."
raise CreatorError(msg)

def _check_collection_path(self) -> None:
"""Validates if the provided path is an Ansible collection.

Raises:
CreatorError: If the path is not a collection path.
"""
galaxy_file_path = self._add_path / "galaxy.yml"
if not Path.is_file(galaxy_file_path):
msg = (
f"The path {self._add_path} is not a valid Ansible collection path. "
"Please provide the root path of a valid ansible collection."
)
raise CreatorError(msg)

def unique_name_in_devfile(self) -> str:
"""Use project specific name in devfile.

Expand All @@ -68,7 +90,7 @@ def unique_name_in_devfile(self) -> str:
final_uuid = str(uuid.uuid4())[:8]
return f"{final_name}-{final_uuid}"

def _scaffold(self) -> None:
def _resource_scaffold(self) -> None:
"""Scaffold the specified resource file based on the resource type.

Raises:
Expand All @@ -84,9 +106,9 @@ def _scaffold(self) -> None:
msg = f"Unsupported resource type: {self._resource_type}"
raise CreatorError(msg)

self._perform_scaffold(template_data)
self._perform_resource_scaffold(template_data)

def _perform_scaffold(self, template_data: TemplateData) -> None:
def _perform_resource_scaffold(self, template_data: TemplateData) -> None:
"""Perform the actual scaffolding process using the provided template data.

Args:
Expand Down Expand Up @@ -136,6 +158,78 @@ def _perform_scaffold(self, template_data: TemplateData) -> None:

self.output.note(f"Resource added to {self._add_path}")

def _plugin_scaffold(self, plugin_path: Path) -> None:
"""Scaffold the specified plugin file based on the plugin type.

Args:
plugin_path: Path where the plugin will be scaffolded.

Raises:
CreatorError: If unsupported plugin type is given.
"""
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()

else:
msg = f"Unsupported plugin type: {self._plugin_type}"
raise CreatorError(msg)

self._perform_plugin_scaffold(template_data, plugin_path)

def _perform_plugin_scaffold(self, template_data: TemplateData, plugin_path: Path) -> None:
"""Perform the actual scaffolding process using the provided template data.

Args:
template_data: TemplateData
plugin_path: Path where the plugin will be scaffolded.

Raises:
CreatorError: If there are conflicts and overwriting is not allowed, or if the
destination directory contains files that will be overwritten.
"""
walker = Walker(
resources=(f"collection_project.plugins.{self._plugin_type}",),
resource_id=self._plugin_id,
dest=plugin_path,
output=self.output,
template_data=template_data,
templar=self.templar,
)
paths = walker.collect_paths()
copier = Copier(output=self.output)

if self._no_overwrite:
msg = "The flag `--no-overwrite` restricts overwriting."
if paths.has_conflicts():
msg += (
"\nThe destination directory contains files that can be overwritten."
"\nPlease re-run ansible-creator with --overwrite to continue."
)
raise CreatorError(msg)

if not paths.has_conflicts() or self._force or self._overwrite:
copier.copy_containers(paths)
self.output.note(f"Plugin added to {plugin_path}")
return

if not self._overwrite:
question = (
"Files in the destination directory will be overwritten. Do you want to proceed?"
)
if ask_yes_no(question):
copier.copy_containers(paths)
else:
msg = (
"The destination directory contains files that will be overwritten."
" Please re-run ansible-creator with --overwrite to continue."
)
raise CreatorError(msg)

self.output.note(f"Plugin added to {plugin_path}")

def _get_devfile_template_data(self) -> TemplateData:
"""Get the template data for devfile resources.

Expand All @@ -147,3 +241,15 @@ def _get_devfile_template_data(self) -> TemplateData:
creator_version=self._creator_version,
dev_file_name=self.unique_name_in_devfile(),
)

def _get_lookup_plugin_template_data(self) -> TemplateData:
"""Get the template data for lookup plugin.

Returns:
TemplateData: Data required for templating the lookup plugin.
"""
return TemplateData(
plugin_type=self._plugin_type,
plugin_name=self._plugin_name,
creator_version=self._creator_version,
)
4 changes: 4 additions & 0 deletions src/ansible_creator/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class TemplateData:

Attributes:
resource_type: The type of resource to be scaffolded.
plugin_type: The type of plugin to be scaffolded.
plugin_name: The name of the plugin to be scaffolded.
additions: A dictionary containing additional data to add to the gitignore.
collection_name: The name of the collection.
creator_version: The version of the creator.
Expand All @@ -29,6 +31,8 @@ class TemplateData:
"""

resource_type: str = ""
plugin_type: str = ""
plugin_name: str = "hello_world"
additions: dict[str, dict[str, dict[str, str | bool]]] = field(default_factory=dict)
collection_name: str = ""
creator_version: str = ""
Expand Down
1 change: 1 addition & 0 deletions src/ansible_creator/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
PATH_REPLACERS = {
"project_org": "namespace",
"project_repo": "collection_name",
"hello_world": "plugin_name",
}


Expand Down
Empty file.
Loading