Parcourir la source

feat(core): remove Jinja2 default() filter extraction (#1410) (#1416)

- Removed _extract_jinja_default_values() method from Template class
- Removed merge logic for Jinja2 defaults in variables property
- Fixed validate command to handle 3-tuple from LibraryManager.find()
- All defaults must now be explicitly defined in template/module specs
- Updated CHANGELOG.md with removal notice
Christian Lempa il y a 4 mois
Parent
commit
de673db40b

+ 3 - 0
CHANGELOG.md

@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
+### Removed
+- Jinja2 `| default()` filter extraction and merging (#1410) - All defaults must now be defined in template/module specs
+
 ## [0.0.7] - 2025-10-28
 ## [0.0.7] - 2025-10-28
 
 
 ### Added
 ### Added

+ 1 - 1
cli/core/module.py

@@ -1149,7 +1149,7 @@ class Module(ABC):
             invalid_count = 0
             invalid_count = 0
             errors = []
             errors = []
 
 
-            for template_dir, library_name in entries:
+            for template_dir, library_name, _ in entries:
                 template_id = template_dir.name
                 template_id = template_dir.name
                 try:
                 try:
                     template = Template(template_dir, library_name=library_name)
                     template = Template(template_dir, library_name=library_name)

+ 0 - 66
cli/core/template.py

@@ -19,8 +19,6 @@ import os
 import yaml
 import yaml
 from jinja2 import Environment, FileSystemLoader, meta
 from jinja2 import Environment, FileSystemLoader, meta
 from jinja2.sandbox import SandboxedEnvironment
 from jinja2.sandbox import SandboxedEnvironment
-from jinja2 import nodes
-from jinja2.visitor import NodeVisitor
 from jinja2.exceptions import (
 from jinja2.exceptions import (
     TemplateSyntaxError as Jinja2TemplateSyntaxError,
     TemplateSyntaxError as Jinja2TemplateSyntaxError,
     UndefinedError,
     UndefinedError,
@@ -554,52 +552,6 @@ class Template:
 
 
         return used_variables
         return used_variables
 
 
-    def _extract_jinja_default_values(self) -> dict[str, object]:
-        """Scan all .j2 files and extract literal arguments to the `default` filter.
-
-        Returns a mapping var_name -> literal_value for simple cases like
-        {{ var | default("value") }} or {{ var | default(123) }}.
-        This does not attempt to evaluate complex expressions.
-        """
-
-        class _DefaultVisitor(NodeVisitor):
-            def __init__(self):
-                self.found: dict[str, object] = {}
-
-            def visit_Filter(self, node: nodes.Filter) -> None:  # type: ignore[override]
-                try:
-                    if getattr(node, "name", None) == "default" and node.args:
-                        # target variable name when filter is applied directly to a Name
-                        target = None
-                        if isinstance(node.node, nodes.Name):
-                            target = node.node.name
-
-                        # first arg literal
-                        first = node.args[0]
-                        if isinstance(first, nodes.Const) and target:
-                            self.found[target] = first.value
-                except Exception:
-                    # Be resilient to unexpected node shapes
-                    pass
-                # continue traversal
-                self.generic_visit(node)
-
-        visitor = _DefaultVisitor()
-
-        for template_file in self.template_files:
-            if template_file.file_type != "j2":
-                continue
-            file_path = self.template_dir / template_file.relative_path
-            try:
-                with open(file_path, "r", encoding="utf-8") as f:
-                    content = f.read()
-                ast = self.jinja_env.parse(content)
-                visitor.visit(ast)
-            except (IOError, OSError, yaml.YAMLError):
-                # Skip failures - this extraction is best-effort only
-                continue
-
-        return visitor.found
 
 
     def _filter_specs_to_used(
     def _filter_specs_to_used(
         self,
         self,
@@ -944,24 +896,6 @@ class Template:
                 self.template_specs,
                 self.template_specs,
             )
             )
 
 
-            # Best-effort: extract literal defaults from Jinja `default()` filter and
-            # merge them into the filtered_specs when no default exists there.
-            try:
-                jinja_defaults = self._extract_jinja_default_values()
-                for section_key, section_data in filtered_specs.items():
-                    # Guard against None from empty YAML sections
-                    vars_dict = section_data.get("vars") or {}
-                    for var_name, var_data in vars_dict.items():
-                        if "default" not in var_data or var_data.get("default") in (
-                            None,
-                            "",
-                        ):
-                            if var_name in jinja_defaults:
-                                var_data["default"] = jinja_defaults[var_name]
-            except (KeyError, TypeError, AttributeError):
-                # Keep behavior stable on any extraction errors
-                pass
-
             self.__variables = VariableCollection(filtered_specs)
             self.__variables = VariableCollection(filtered_specs)
             # Sort sections: required first, then enabled, then disabled
             # Sort sections: required first, then enabled, then disabled
             self.__variables.sort_sections()
             self.__variables.sort_sections()

+ 24 - 11
library/compose/traefik/compose.yaml.j2

@@ -4,14 +4,12 @@ services:
     {% if not swarm_enabled %}
     {% if not swarm_enabled %}
     container_name: {{ container_name }}
     container_name: {{ container_name }}
     {% endif %}
     {% endif %}
-    {% if ports_enabled %}
     ports:
     ports:
-      - "80:80"
-      - "443:443"
-      {% if traefik_dashboard_enabled %}
-      - "8080:8080"  # Dashboard (don't use in production)
+      - "{{ ports_http }}:80"
+      - "{{ ports_https }}:443"
+      {% if dashboard_enabled %}
+      - "{{ ports_dashboard }}:8080"
       {% endif %}
       {% endif %}
-    {% endif %}
     volumes:
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock:ro
       - /var/run/docker.sock:/var/run/docker.sock:ro
       {% if not swarm_enabled %}
       {% if not swarm_enabled %}
@@ -52,9 +50,14 @@ services:
       timeout: 5s
       timeout: 5s
       retries: 3
       retries: 3
       start_period: 10s
       start_period: 10s
-    {% if network_enabled %}
+    {% if network_mode == 'host' %}
+    network_mode: host
+    {% else %}
     networks:
     networks:
-      - {{ network_name }}
+      {{ traefik_network }}:
+        {% if network_mode == 'macvlan' %}
+        ipv4_address: {{ network_macvlan_ipv4_address }}
+        {% endif %}
     {% endif %}
     {% endif %}
     {% if swarm_enabled %}
     {% if swarm_enabled %}
     {% if traefik_tls_enabled %}
     {% if traefik_tls_enabled %}
@@ -107,9 +110,9 @@ secrets:
 {% endif %}
 {% endif %}
 {% endif %}
 {% endif %}
 
 
-{% if network_enabled %}
+{% if network_mode != 'host' %}
 networks:
 networks:
-  {{ network_name }}:
+  {{ traefik_network }}:
     {% if network_external %}
     {% if network_external %}
     external: true
     external: true
     {% else %}
     {% else %}
@@ -117,8 +120,18 @@ networks:
     driver: overlay
     driver: overlay
     attachable: true
     attachable: true
     {% else %}
     {% else %}
+    {% if network_mode == 'macvlan' %}
+    driver: macvlan
+    driver_opts:
+      parent: {{ network_macvlan_parent_interface }}
+    ipam:
+      config:
+        - subnet: {{ network_macvlan_subnet }}
+          gateway: {{ network_macvlan_gateway }}
+    {% else %}
     driver: bridge
     driver: bridge
     {% endif %}
     {% endif %}
-    name: {{ network_name }}
+    {% endif %}
+    name: {{ traefik_network }}
     {% endif %}
     {% endif %}
 {% endif %}
 {% endif %}

+ 2 - 2
library/compose/traefik/config/traefik.yaml.j2

@@ -12,7 +12,7 @@ log:
 accesslog: {}
 accesslog: {}
 {% endif %}
 {% endif %}
 
 
-{% if traefik_dashboard_enabled %}
+{% if dashboard_enabled %}
 api:
 api:
   dashboard: true
   dashboard: true
   insecure: true
   insecure: true
@@ -21,7 +21,7 @@ api:
 entryPoints:
 entryPoints:
   {{ traefik_entrypoint }}:
   {{ traefik_entrypoint }}:
     address: :80
     address: :80
-    {% if traefik_tls_enabled and traefik_tls_redirect %}
+    {% if traefik_tls_enabled and tls_redirect %}
     http:
     http:
       redirections:
       redirections:
         entryPoint:
         entryPoint:

+ 38 - 83
library/compose/traefik/template.yaml

@@ -20,44 +20,6 @@ metadata:
   next_steps: |
   next_steps: |
     1. Start Traefik:
     1. Start Traefik:
        docker compose up -d
        docker compose up -d
-
-    2. Configure your domain DNS:
-       - Point your domain A/AAAA records to your server IP
-       {% if traefik_tls_enabled -%}
-       - Configure DNS API credentials in .env file
-       - Ensure {{ traefik_tls_acme_provider }} API token has DNS edit permissions
-       {%- endif %}
-
-    3. Access the dashboard:
-       {% if traefik_dashboard_enabled -%}
-       - Dashboard: http://localhost:8080
-       - WARNING: Dashboard is in insecure mode - don't use in production!
-       {%- else -%}
-       - Dashboard is disabled (secure production setup)
-       - Enable it temporarily by setting traefik_dashboard_enabled=true
-       {%- endif %}
-
-    4. Deploy your services:
-       - Ensure services use the '{{ network_name }}' network
-       - Add Traefik labels to your service containers
-       - Services will be automatically discovered and routed
-
-    5. Configuration files:
-       - Static config: config/traefik.yml
-       - Dynamic config: config/conf.d/*.yml
-       {% if traefik_tls_enabled -%}
-       - TLS certificates: certs/acme.json
-       {%- endif %}
-
-    6. Security recommendations:
-       - Disable dashboard in production (traefik_dashboard_enabled=false)
-       - Use TLS/HTTPS for all services
-       - Store API tokens in Docker secrets (Swarm) or secure vaults
-       - Regularly update Traefik to latest version
-       - Review and limit network exposure
-
-    For more information, visit: https://doc.traefik.io/traefik/
-  draft: true
 spec:
 spec:
   general:
   general:
     title: "General"
     title: "General"
@@ -67,28 +29,44 @@ spec:
         default: "traefik"
         default: "traefik"
       container_name:
       container_name:
         default: "traefik"
         default: "traefik"
-      accesslog_enabled:
-        type: "bool"
-        description: "Enable Traefik access log"
-        default: false
+  ports:
+    title: "Ports"
+    description: "Configure external port mappings"
+    vars:
+      ports_http:
+        type: "int"
+        description: "HTTP port (external)"
+        default: 80
+        extra: "Maps to entrypoint 'web' (port 80)"
+      ports_https:
+        type: "int"
+        description: "HTTPS port (external)"
+        default: 443
+        extra: "Maps to entrypoint 'websecure' (port 443)"
+      ports_dashboard:
+        type: "int"
+        description: "Dashboard port (external)"
+        default: 8080
+        extra: "Only used when dashboard is enabled"
   traefik:
   traefik:
-    title: "Traefik Settings"
-    description: "Configure Traefik as a reverse proxy"
-    required: true
+    title: "Settings"
     vars:
     vars:
-      traefik_entrypoint:
+      traefik_network:
         type: "str"
         type: "str"
-        description: "HTTP entrypoint name (non-TLS)"
-        default: "web"
-        extra: "Standard HTTP traffic on port 80"
-      traefik_dashboard_enabled:
+        description: "Traefik network name"
+        default: "traefik"
+        extra: "Network that Traefik uses to connect to services"
+      dashboard_enabled:
         type: "bool"
         type: "bool"
-        description: "Enable Traefik dashboard (insecure mode)"
+        description: "Enable Traefik dashboard"
+        default: false
+        extra: "WARNING: Don't use in production!"
+      accesslog_enabled:
+        type: "bool"
+        description: "Enable Traefik access log"
         default: false
         default: false
-        extra: "WARNING: Don't use in production! Exposes dashboard on port 8080"
   traefik_tls:
   traefik_tls:
-    title: "Traefik TLS Settings"
-    description: "Configure TLS/SSL with Let's Encrypt ACME"
+    title: "TLS Settings"
     needs: null
     needs: null
     vars:
     vars:
       traefik_tls_enabled:
       traefik_tls_enabled:
@@ -106,44 +84,21 @@ spec:
         type: "str"
         type: "str"
         description: "DNS provider API token"
         description: "DNS provider API token"
         sensitive: true
         sensitive: true
-        extra: "For Cloudflare, create an API token with Zone:DNS:Edit permissions. Leave empty to use Docker Swarm secrets."
-      traefik_tls_acme_secret_name:
-        type: "str"
-        description: "Docker Swarm secret name for API token (swarm mode only)"
-        default: "cloudflare_api_token"
-        extra: "The secret name to use in Docker Swarm for storing the API token"
       traefik_tls_acme_email:
       traefik_tls_acme_email:
         type: "str"
         type: "str"
         description: "Email address for ACME (Let's Encrypt) registration"
         description: "Email address for ACME (Let's Encrypt) registration"
         default: "admin@example.com"
         default: "admin@example.com"
         extra: "Required for Let's Encrypt certificate requests"
         extra: "Required for Let's Encrypt certificate requests"
-      traefik_tls_redirect:
+      tls_redirect:
         type: "bool"
         type: "bool"
         description: "Redirect all HTTP traffic to HTTPS"
         description: "Redirect all HTTP traffic to HTTPS"
         default: true
         default: true
-  ports:
-    toggle: "ports_enabled"
-    vars:
-      traefik_http_port:
-        type: "int"
-        description: "HTTP port (external)"
-        default: 80
-        extra: "Maps to entrypoint 'web' (port 80)"
-      traefik_https_port:
-        type: "int"
-        description: "HTTPS port (external)"
-        default: 443
-        extra: "Maps to entrypoint 'websecure' (port 443)"
-  network:
+  swarm:
     vars:
     vars:
-      network_enabled:
-        default: true
-      network_mode:
-        default: "bridge"
-      network_name:
-        default: "proxy"
-      network_external:
-        default: false
+      traefik_tls_acme_secret_name:
+        type: "str"
+        description: "Docker Swarm secret name for API token"
+        default: "cloudflare_api_token"
   authentik:
   authentik:
     title: Authentik Middleware
     title: Authentik Middleware
     description: Enable Authentik SSO integration for Traefik
     description: Enable Authentik SSO integration for Traefik