diff --git a/src/ansible_creator/constants.py b/src/ansible_creator/constants.py index bca45fcb..781796a2 100644 --- a/src/ansible_creator/constants.py +++ b/src/ansible_creator/constants.py @@ -3,6 +3,7 @@ GLOBAL_TEMPLATE_VARS = { "DEV_CONTAINER_IMAGE": "ghcr.io/ansible/community-ansible-dev-tools:latest", "DEV_FILE_IMAGE": "ghcr.io/ansible/ansible-workspace-env-reference:latest", + "RECOMMENDED_EXTENSIONS": ["redhat.ansible", "redhat.vscode-redhat-account"], } MIN_COLLECTION_NAME_LEN = 2 diff --git a/src/ansible_creator/resources/ansible_project/.vscode/extensions.json b/src/ansible_creator/resources/ansible_project/.vscode/extensions.json deleted file mode 100644 index 51360a49..00000000 --- a/src/ansible_creator/resources/ansible_project/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["redhat.ansible"] -} diff --git a/src/ansible_creator/resources/common/devcontainer/.devcontainer/devcontainer.json.j2 b/src/ansible_creator/resources/common/devcontainer/.devcontainer/devcontainer.json.j2 index 96689ffe..bf58332b 100644 --- a/src/ansible_creator/resources/common/devcontainer/.devcontainer/devcontainer.json.j2 +++ b/src/ansible_creator/resources/common/devcontainer/.devcontainer/devcontainer.json.j2 @@ -1,6 +1,6 @@ { "name": "ansible-dev-container-codespaces", - "image": "{{ DEV_CONTAINER_IMAGE }}", + "image": "{{ dev_container_image }}", "containerUser": "podman", "runArgs": [ "--security-opt", @@ -18,7 +18,7 @@ "updateRemoteUserUID": true, "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": {{ recommended_extensions | json }} } } } diff --git a/src/ansible_creator/resources/common/devcontainer/.devcontainer/docker/devcontainer.json.j2 b/src/ansible_creator/resources/common/devcontainer/.devcontainer/docker/devcontainer.json.j2 index 2f078393..46696406 100644 --- a/src/ansible_creator/resources/common/devcontainer/.devcontainer/docker/devcontainer.json.j2 +++ b/src/ansible_creator/resources/common/devcontainer/.devcontainer/docker/devcontainer.json.j2 @@ -1,6 +1,6 @@ { "name": "ansible-dev-container-docker", - "image": "{{ DEV_CONTAINER_IMAGE }}", + "image": "{{ dev_container_image }}", "containerUser": "podman", "runArgs": [ "--security-opt", @@ -18,7 +18,7 @@ "updateRemoteUserUID": true, "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": {{ recommended_extensions | json }} } } } diff --git a/src/ansible_creator/resources/common/devcontainer/.devcontainer/podman/devcontainer.json.j2 b/src/ansible_creator/resources/common/devcontainer/.devcontainer/podman/devcontainer.json.j2 index ed8ee27d..3812ca8e 100644 --- a/src/ansible_creator/resources/common/devcontainer/.devcontainer/podman/devcontainer.json.j2 +++ b/src/ansible_creator/resources/common/devcontainer/.devcontainer/podman/devcontainer.json.j2 @@ -1,6 +1,6 @@ { "name": "ansible-dev-container-podman", - "image": "{{ DEV_CONTAINER_IMAGE }}", + "image": "{{ dev_container_image }}", "containerUser": "root", "runArgs": [ "--cap-add=SYS_ADMIN", @@ -20,7 +20,7 @@ ], "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": {{ recommended_extensions | json }} } } } diff --git a/src/ansible_creator/resources/common/devfile/devfile.yaml.j2 b/src/ansible_creator/resources/common/devfile/devfile.yaml.j2 index 323b2039..aca20ba8 100644 --- a/src/ansible_creator/resources/common/devfile/devfile.yaml.j2 +++ b/src/ansible_creator/resources/common/devfile/devfile.yaml.j2 @@ -4,7 +4,7 @@ metadata: components: - name: tooling-container container: - image: {{ DEV_FILE_IMAGE }} + image: {{ dev_file_image }} memoryRequest: 256M memoryLimit: 6Gi cpuRequest: 250m diff --git a/src/ansible_creator/resources/common/vscode/.vscode/extensions.json.j2 b/src/ansible_creator/resources/common/vscode/.vscode/extensions.json.j2 new file mode 100644 index 00000000..e4e80856 --- /dev/null +++ b/src/ansible_creator/resources/common/vscode/.vscode/extensions.json.j2 @@ -0,0 +1,3 @@ +{ + "recommendations": {{ recommended_extensions | json }} +} diff --git a/src/ansible_creator/resources/new_collection/.vscode/extensions.json b/src/ansible_creator/resources/new_collection/.vscode/extensions.json deleted file mode 100644 index 51360a49..00000000 --- a/src/ansible_creator/resources/new_collection/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["redhat.ansible"] -} diff --git a/src/ansible_creator/subcommands/init.py b/src/ansible_creator/subcommands/init.py index a44f6c77..fac6c778 100644 --- a/src/ansible_creator/subcommands/init.py +++ b/src/ansible_creator/subcommands/init.py @@ -9,6 +9,7 @@ from ansible_creator.exceptions import CreatorError from ansible_creator.templar import Templar +from ansible_creator.types import TemplateData from ansible_creator.utils import Copier @@ -89,28 +90,31 @@ def run(self: Init) -> None: # noqa: C901 self.output.debug(msg=f"creating new directory at {self._init_path}") self._init_path.mkdir(parents=True) + common_resources = [ + "common.devcontainer", + "common.devfile", + "common.gitignore", + "common.vscode", + ] + if self._project == "collection": if not isinstance(self._collection_name, str): msg = "Collection name is required when scaffolding a collection." raise CreatorError(msg) # copy new_collection container to destination, templating files when found self.output.debug(msg="started copying collection skeleton to destination") + template_data = TemplateData( + namespace=self._namespace, + collection_name=self._collection_name, + creator_version=self._creator_version, + ) copier = Copier( - resources=[ - "new_collection", - "common.devcontainer", - "common.devfile", - "common.gitignore", - ], + resources=["new_collection", *common_resources], resource_id="new_collection", dest=self._init_path, output=self.output, templar=self._templar, - template_data={ - "namespace": self._namespace, - "collection_name": self._collection_name, - "creator_version": self._creator_version, - }, + template_data=template_data, ) copier.copy_containers() @@ -132,22 +136,20 @@ def run(self: Init) -> None: # noqa: C901 "scaffolding an ansible-project." ) raise CreatorError(msg) + + template_data = TemplateData( + creator_version=self._creator_version, + scm_org=self._scm_org, + scm_project=self._scm_project, + ) + copier = Copier( - resources=[ - "ansible_project", - "common.devcontainer", - "common.devfile", - "common.gitignore", - ], + resources=["ansible_project", *common_resources], resource_id="ansible_project", dest=self._init_path, output=self.output, templar=self._templar, - template_data={ - "scm_org": self._scm_org, - "scm_project": self._scm_project, - "creator_version": self._creator_version, - }, + template_data=template_data, ) copier.copy_containers() diff --git a/src/ansible_creator/templar.py b/src/ansible_creator/templar.py index ea5c33f3..76291f12 100644 --- a/src/ansible_creator/templar.py +++ b/src/ansible_creator/templar.py @@ -2,11 +2,15 @@ from __future__ import annotations +import json + +from dataclasses import asdict from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any + from ansible_creator.types import TemplateData + try: from jinja2 import Environment, StrictUndefined @@ -37,8 +41,9 @@ def __init__(self: Templar) -> None: undefined=StrictUndefined, keep_trailing_newline=True, ) + self.env.filters["json"] = json.dumps - def render_from_content(self: Templar, template: str, data: dict[str, Any]) -> str: + def render_from_content(self: Templar, template: str, data: TemplateData) -> str: """Render a template with provided data. Args: @@ -48,4 +53,4 @@ def render_from_content(self: Templar, template: str, data: dict[str, Any]) -> s Returns: Templated content. """ - return self.env.from_string(template).render(data) + return self.env.from_string(template).render(asdict(data)) diff --git a/src/ansible_creator/types.py b/src/ansible_creator/types.py new file mode 100644 index 00000000..553d8006 --- /dev/null +++ b/src/ansible_creator/types.py @@ -0,0 +1,35 @@ +"""A home for shared types.""" + +from collections.abc import Sequence +from dataclasses import dataclass, field + +from ansible_creator.constants import GLOBAL_TEMPLATE_VARS + + +@dataclass +class TemplateData: + """Dataclass representing the template data. + + Attributes: + 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. + dev_container_image: The devcontainer image. + dev_file_image: The devfile image. + namespace: The namespace of the collection. + recommended_extensions: A list of recommended VsCode extensions. + scm_org: The organization of the source control management. + scm_project: The project of the source control management. + """ + + additions: dict[str, dict[str, dict[str, str | bool]]] = field(default_factory=dict) + collection_name: str = "" + creator_version: str = "" + dev_container_image: Sequence[str] = GLOBAL_TEMPLATE_VARS["DEV_CONTAINER_IMAGE"] + dev_file_image: Sequence[str] = GLOBAL_TEMPLATE_VARS["DEV_FILE_IMAGE"] + namespace: str = "" + recommended_extensions: Sequence[str] = field( + default_factory=lambda: GLOBAL_TEMPLATE_VARS["RECOMMENDED_EXTENSIONS"], + ) + scm_org: str = "" + scm_project: str = "" diff --git a/src/ansible_creator/utils.py b/src/ansible_creator/utils.py index 1258af5d..74d59544 100644 --- a/src/ansible_creator/utils.py +++ b/src/ansible_creator/utils.py @@ -2,22 +2,26 @@ from __future__ import annotations +import copy import os -from dataclasses import dataclass, field +from dataclasses import dataclass from importlib import resources as impl_resources from pathlib import Path from typing import TYPE_CHECKING import yaml -from ansible_creator.constants import GLOBAL_TEMPLATE_VARS, SKIP_DIRS, SKIP_FILES_TYPES +from ansible_creator.constants import SKIP_DIRS, SKIP_FILES_TYPES if TYPE_CHECKING: + from ansible_creator.compat import Traversable from ansible_creator.output import Output from ansible_creator.templar import Templar + from ansible_creator.types import TemplateData + PATH_REPLACERS = { "project_org": "scm_org", @@ -69,22 +73,22 @@ class Copier: resource_id: The id of the resource to copy. dest: The destination path to copy resources to. output: An instance of the Output class. + template_data: A dictionary containing the original data to render templates with. allow_overwrite: A list of paths that should be overwritten at destination. index: Index of the current resource being copied. resource_root: Root path for the resources. templar: An instance of the Templar class. - template_data: A dictionary containing the original data to render templates with. """ resources: list[str] resource_id: str dest: Path output: Output + template_data: TemplateData allow_overwrite: list[str] | None = None index: int = 0 resource_root: str = "ansible_creator.resources" templar: Templar | None = None - template_data: dict[str, str] = field(default_factory=dict) @property def resource(self: Copier) -> str: @@ -94,7 +98,7 @@ def resource(self: Copier) -> str: def _recursive_copy( # noqa: C901, PLR0912 self: Copier, root: Traversable, - template_data: dict[str, str], + template_data: TemplateData, ) -> None: """Recursively traverses a resource container and copies content to destination. @@ -118,7 +122,7 @@ def _recursive_copy( # noqa: C901, PLR0912 for key, val in PATH_REPLACERS.items(): if key in str(dest_path) and template_data: str_dest_path = str(dest_path) - repl_val = template_data.get(val, "") + repl_val = getattr(template_data, val) dest_path = Path(str_dest_path.replace(key, repl_val)) if obj.is_dir(): @@ -171,11 +175,8 @@ def _per_container(self: Copier) -> None: ) self.output.debug(msg=f"allow_overwrite set to {self.allow_overwrite}") - # Include the global template variables - self.template_data.update(GLOBAL_TEMPLATE_VARS) - - # Copy the template data to not pollute the original - template_data = self.template_data.copy() + # Cast the template data to not pollute the original + template_data = copy.deepcopy(self.template_data) # Collect and template any resource specific variables meta_file = impl_resources.files(f"{self.resource_root}.{self.resource}") / "__meta__.yml" @@ -198,9 +199,9 @@ def _per_container(self: Copier) -> None: data=template_data, ) deserialized = yaml.safe_load(templated) - template_data.update({key: deserialized}) + setattr(template_data, key, deserialized) else: - template_data.update({key: value["value"]}) + setattr(template_data, key, value["value"]) self._recursive_copy( root=impl_resources.files(f"{self.resource_root}.{self.resource}"), diff --git a/tests/fixtures/collection/testorg/testcol/.devcontainer/devcontainer.json b/tests/fixtures/collection/testorg/testcol/.devcontainer/devcontainer.json index e14c0bb8..4443c9e0 100644 --- a/tests/fixtures/collection/testorg/testcol/.devcontainer/devcontainer.json +++ b/tests/fixtures/collection/testorg/testcol/.devcontainer/devcontainer.json @@ -18,7 +18,7 @@ "updateRemoteUserUID": true, "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": ["redhat.ansible", "redhat.vscode-redhat-account"] } } } diff --git a/tests/fixtures/collection/testorg/testcol/.devcontainer/docker/devcontainer.json b/tests/fixtures/collection/testorg/testcol/.devcontainer/docker/devcontainer.json index 18b5ef34..48d612ee 100644 --- a/tests/fixtures/collection/testorg/testcol/.devcontainer/docker/devcontainer.json +++ b/tests/fixtures/collection/testorg/testcol/.devcontainer/docker/devcontainer.json @@ -18,7 +18,7 @@ "updateRemoteUserUID": true, "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": ["redhat.ansible", "redhat.vscode-redhat-account"] } } } diff --git a/tests/fixtures/collection/testorg/testcol/.devcontainer/podman/devcontainer.json b/tests/fixtures/collection/testorg/testcol/.devcontainer/podman/devcontainer.json index 30327b46..a11a3dfc 100644 --- a/tests/fixtures/collection/testorg/testcol/.devcontainer/podman/devcontainer.json +++ b/tests/fixtures/collection/testorg/testcol/.devcontainer/podman/devcontainer.json @@ -20,7 +20,7 @@ ], "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": ["redhat.ansible", "redhat.vscode-redhat-account"] } } } diff --git a/tests/fixtures/collection/testorg/testcol/.vscode/extensions.json b/tests/fixtures/collection/testorg/testcol/.vscode/extensions.json index 51360a49..c1b89785 100644 --- a/tests/fixtures/collection/testorg/testcol/.vscode/extensions.json +++ b/tests/fixtures/collection/testorg/testcol/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["redhat.ansible"] + "recommendations": ["redhat.ansible", "redhat.vscode-redhat-account"] } diff --git a/tests/fixtures/project/ansible_project/.devcontainer/devcontainer.json b/tests/fixtures/project/ansible_project/.devcontainer/devcontainer.json index e14c0bb8..4443c9e0 100644 --- a/tests/fixtures/project/ansible_project/.devcontainer/devcontainer.json +++ b/tests/fixtures/project/ansible_project/.devcontainer/devcontainer.json @@ -18,7 +18,7 @@ "updateRemoteUserUID": true, "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": ["redhat.ansible", "redhat.vscode-redhat-account"] } } } diff --git a/tests/fixtures/project/ansible_project/.devcontainer/docker/devcontainer.json b/tests/fixtures/project/ansible_project/.devcontainer/docker/devcontainer.json index 18b5ef34..48d612ee 100644 --- a/tests/fixtures/project/ansible_project/.devcontainer/docker/devcontainer.json +++ b/tests/fixtures/project/ansible_project/.devcontainer/docker/devcontainer.json @@ -18,7 +18,7 @@ "updateRemoteUserUID": true, "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": ["redhat.ansible", "redhat.vscode-redhat-account"] } } } diff --git a/tests/fixtures/project/ansible_project/.devcontainer/podman/devcontainer.json b/tests/fixtures/project/ansible_project/.devcontainer/podman/devcontainer.json index 30327b46..a11a3dfc 100644 --- a/tests/fixtures/project/ansible_project/.devcontainer/podman/devcontainer.json +++ b/tests/fixtures/project/ansible_project/.devcontainer/podman/devcontainer.json @@ -20,7 +20,7 @@ ], "customizations": { "vscode": { - "extensions": ["redhat.ansible"] + "extensions": ["redhat.ansible", "redhat.vscode-redhat-account"] } } } diff --git a/tests/fixtures/project/ansible_project/.vscode/extensions.json b/tests/fixtures/project/ansible_project/.vscode/extensions.json index 51360a49..c1b89785 100644 --- a/tests/fixtures/project/ansible_project/.vscode/extensions.json +++ b/tests/fixtures/project/ansible_project/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["redhat.ansible"] + "recommendations": ["redhat.ansible", "redhat.vscode-redhat-account"] } diff --git a/tests/units/test_templar.py b/tests/units/test_templar.py new file mode 100644 index 00000000..25f33842 --- /dev/null +++ b/tests/units/test_templar.py @@ -0,0 +1,28 @@ +"""Tests for templar.""" + +from ansible_creator.templar import Templar +from ansible_creator.types import TemplateData + + +def test_templar() -> None: + """Test templar.""" + templar = Templar() + data = TemplateData(collection_name="test") + template = "{{ collection_name }}" + assert templar.render_from_content(template, data) == "test" + + +def test_templar_json_simple() -> None: + """Test templar json with a simple structure.""" + templar = Templar() + data = TemplateData(recommended_extensions=["value"]) + template = "{{ recommended_extensions | json }}" + assert templar.render_from_content(template, data) == '["value"]' + + +def test_templar_json_complex() -> None: + """Test templar json with a complex structure.""" + templar = Templar() + data = TemplateData(additions={"key": {"key": {"key": True}}}) + template = "{{ additions | json }}" + assert templar.render_from_content(template, data) == '{"key": {"key": {"key": true}}}'