Просмотр исходного кода

fixed #1522 and archetype improvements

xcad 3 месяцев назад
Родитель
Сommit
0025f118d4

+ 43 - 66
AGENTS.md

@@ -513,88 +513,65 @@ To skip the prompt use the `--no-interactive` flag, which will use defaults or e
 - `repo sync` - Sync git-based libraries
 - `repo sync` - Sync git-based libraries
 - `repo list` - List configured libraries
 - `repo list` - List configured libraries
 
 
-## Archetypes Testing Tool
+## Archetypes
 
 
-The `archetypes` package provides a testing tool for developing and testing individual template snippets (Jinja2 files) without needing a full template directory structure.
+The `archetypes` package provides reusable, standardized template building blocks for creating boilerplates. Archetypes are modular Jinja2 snippets that represent specific configuration sections (e.g., networks, volumes, service labels).
 
 
 ### Purpose
 ### Purpose
 
 
-Archetypes are template "snippets" or "parts" that can be tested in isolation. This is useful for:
-- Developing specific sections of templates (e.g., network configurations, volume mounts)
-- Testing Jinja2 logic with different variable combinations
-- Validating template rendering before integrating into full templates
+1. **Template Development**: Provide standardized, tested building blocks for creating new templates
+2. **Testing & Validation**: Enable testing of specific configuration sections in isolation with different variable combinations
 
 
-### Usage
-
-```bash
-# Run the archetypes tool
-python3 -m archetypes
+### Structure
 
 
-# List all archetypes for a module
-python3 -m archetypes compose list
+```
+archetypes/
+  __init__.py              # Package initialization
+  __main__.py              # CLI tool (auto-discovers modules)
+  compose/                 # Module-specific archetypes
+    archetypes.yaml        # Configuration: schema version + variable overrides
+    compose.yaml.j2        # Main composition file (includes all components)
+    service-*.j2           # Service-level components (networks, ports, volumes, labels, etc.)
+    networks-*.j2          # Top-level network definitions
+    volumes-*.j2           # Top-level volume definitions
+    configs-*.j2           # Config definitions
+    secrets-*.j2           # Secret definitions
+```
 
 
-# Show details of an archetype (displays variables and content)
-python3 -m archetypes compose show network-v1
+**Key Files:**
+- `archetypes.yaml`: Configures schema version and variable overrides for testing
+- `compose.yaml.j2`: Main composition file that includes all archetype components to test complete configurations
+- Individual `*.j2` files: Modular components for specific configuration sections
 
 
-# Preview generated output (always in preview mode - never writes files)
-python3 -m archetypes compose generate network-v1
+### Usage
 
 
-# Preview with variable overrides
-python3 -m archetypes compose generate network-v1 \
-  --var network_mode=macvlan \
-  --var network_macvlan_ipv4_address=192.168.1.100
+```bash
+# List available archetypes
+python3 -m archetypes compose list
 
 
-# Preview with reference directory (for context only - no files written)
-python3 -m archetypes compose generate network-v1 /tmp/output --var network_mode=host
-```
+# Preview complete composition (all components together)
+python3 -m archetypes compose generate compose.yaml
 
 
-### Structure
+# Preview individual component
+python3 -m archetypes compose generate networks-v1
 
 
-```
-archetypes/
-  __init__.py           # Package initialization
-  __main__.py           # CLI tool (auto-discovers modules)
-  compose/              # Module-specific archetypes
-    network-v1.j2       # Archetype snippet (just a .j2 file)
-    volumes-v1.j2       # Another archetype
-  terraform/            # Another module's archetypes
-    vpc.j2
+# Test with variable overrides
+python3 -m archetypes compose generate compose.yaml \
+  --var traefik_enabled=true \
+  --var swarm_enabled=true
 ```
 ```
 
 
-### Key Features
+### Template Development Workflow
 
 
-- **Auto-discovers modules**: Scans `archetypes/` for subdirectories (module names)
-- **Reuses CLI components**: Imports actual CLI classes (Template, VariableCollection, DisplayManager) for identical behavior
-- **Loads module specs**: Pulls variable specifications from `cli/modules/<module>/spec_v*.py` for defaults
-- **Full variable context**: Provides ALL variables with defaults (not just satisfied ones) for complete rendering
-- **Three commands**: `list`, `show`, `generate`
-- **Testing only**: The `generate` command NEVER writes files - it always shows preview output only
+1. **Start with archetypes**: Copy relevant archetype components to your template directory
+2. **Customize**: Modify components as needed (hardcode image, add custom labels, etc.)
+3. **Test**: Validate using `python3 -m archetypes compose generate`
+4. **Validate**: Use `compose validate` to check Jinja2 syntax and semantic correctness
 
 
 ### Implementation Details
 ### Implementation Details
 
 
 **How it works:**
 **How it works:**
-1. Module discovery: Finds subdirectories in `archetypes/` (e.g., `compose`)
-2. For each module, creates a Typer sub-app with list/show/generate commands
-3. Archetype files are simple `.j2` files (no `template.yaml` needed)
-4. Variable defaults come from module spec: `cli/modules/<module>/spec_v*.py`
-5. Rendering uses Jinja2 with full variable context from spec
-
-**ArchetypeTemplate class:**
-- Simplified template wrapper for single .j2 files
-- Loads module spec and converts to VariableCollection
-- Extracts ALL variables (not just satisfied) from spec sections
-- Merges user overrides (`--var`) on top of spec defaults
-- Renders using Jinja2 FileSystemLoader
-
-**Variable defaults source:**
-```python
-# Defaults come from module spec files
-from cli.modules.compose import spec  # OrderedDict with variable definitions
-vc = VariableCollection(spec)         # Convert to VariableCollection
-
-# Extract all variables with their default values
-for section_name, section in vc._sections.items():
-    for var_name, var in section.variables.items():
-        if var.value is not None:  # var.value stores the default
-            render_context[var_name] = var.value
-```
+- Loads module spec based on schema version from `archetypes.yaml`
+- Merges variable sources: module spec → archetypes.yaml → CLI --var
+- Renders using Jinja2 with support for `{% include %}` directives
+- **Testing only**: The `generate` command NEVER writes files - always shows preview output

+ 3 - 0
CHANGELOG.md

@@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Improved debug logging to capture module discovery and registration during initialization
 - Improved debug logging to capture module discovery and registration during initialization
 - Enhanced debug logging for better troubleshooting
 - Enhanced debug logging for better troubleshooting
 
 
+### Fixed
+- CLI --var flag now properly converts boolean and numeric strings to appropriate Python types (#1522)
+
 ## [0.0.7] - 2025-10-28
 ## [0.0.7] - 2025-10-28
 
 
 ### Added
 ### Added

+ 67 - 48
archetypes/__main__.py

@@ -9,6 +9,7 @@ from __future__ import annotations
 import builtins
 import builtins
 import importlib
 import importlib
 import logging
 import logging
+import os
 import sys
 import sys
 from collections import OrderedDict
 from collections import OrderedDict
 from pathlib import Path
 from pathlib import Path
@@ -17,8 +18,11 @@ from typing import Any
 import click
 import click
 import yaml
 import yaml
 
 
+# Add parent directory to Python path for CLI imports
+# This allows archetypes to import from cli module when run as `python3 -m archetypes`
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
+
 # Import CLI components
 # Import CLI components
-from cli.core.collection import VariableCollection
 from jinja2 import Environment, FileSystemLoader, StrictUndefined
 from jinja2 import Environment, FileSystemLoader, StrictUndefined
 from rich.console import Console
 from rich.console import Console
 from rich.panel import Panel
 from rich.panel import Panel
@@ -29,6 +33,8 @@ from cli.core.display import DisplayManager
 from cli.core.exceptions import (
 from cli.core.exceptions import (
     TemplateRenderError,
     TemplateRenderError,
 )
 )
+from cli.core.module.helpers import parse_var_inputs
+from cli.core.template.variable_collection import VariableCollection
 
 
 app = Typer(
 app = Typer(
     help="Test and develop template snippets (archetypes) without full template structure.",
     help="Test and develop template snippets (archetypes) without full template structure.",
@@ -82,9 +88,14 @@ class ArchetypeTemplate:
         self.variables = self._load_module_spec()
         self.variables = self._load_module_spec()
 
 
     def _load_module_spec(self) -> VariableCollection | None:
     def _load_module_spec(self) -> VariableCollection | None:
-        """Load variable spec from the module and merge with extension.yaml if present."""
+        """Load variable spec from module and merge with archetypes.yaml if present."""
         try:
         try:
-            spec = self._import_module_spec()
+            # Load archetype config to get schema version
+            archetype_config = self._load_archetype_config()
+            schema_version = archetype_config.get("schema", "1.0") if archetype_config else "1.0"
+            
+            # Import module spec with correct schema
+            spec = self._import_module_spec(schema_version)
             if spec is None:
             if spec is None:
                 return None
                 return None
 
 
@@ -92,18 +103,42 @@ class ArchetypeTemplate:
             if spec_dict is None:
             if spec_dict is None:
                 return None
                 return None
 
 
-            self._merge_extension_file(spec_dict)
+            # Merge variables from archetypes.yaml
+            if archetype_config and "vars" in archetype_config:
+                self._merge_archetype_vars(spec_dict, archetype_config["vars"])
+            
             return VariableCollection(spec_dict)
             return VariableCollection(spec_dict)
         except Exception as e:
         except Exception as e:
             logging.warning(f"Could not load spec for module {self.module_name}: {e}")
             logging.warning(f"Could not load spec for module {self.module_name}: {e}")
             return None
             return None
 
 
-    def _import_module_spec(self) -> Any | None:
-        """Import module and retrieve spec attribute."""
+    def _load_archetype_config(self) -> dict | None:
+        """Load archetypes.yaml configuration file."""
+        config_file = self.template_dir / "archetypes.yaml"
+        if not config_file.exists():
+            return None
+        
+        try:
+            with config_file.open() as f:
+                return yaml.safe_load(f)
+        except Exception as e:
+            logging.warning(f"Failed to load archetypes.yaml: {e}")
+            return None
+
+    def _import_module_spec(self, schema_version: str) -> Any | None:
+        """Import module spec for the specified schema version."""
         module_path = f"cli.modules.{self.module_name}"
         module_path = f"cli.modules.{self.module_name}"
         try:
         try:
             module = importlib.import_module(module_path)
             module = importlib.import_module(module_path)
-            spec = getattr(module, "spec", None)
+            
+            # Try to get schema-specific spec if module supports it
+            if hasattr(module, "SCHEMAS") and schema_version in module.SCHEMAS:
+                spec = module.SCHEMAS[schema_version]
+                logging.debug(f"Using schema {schema_version} for module {self.module_name}")
+            else:
+                # Fall back to default spec
+                spec = getattr(module, "spec", None)
+            
             if spec is None:
             if spec is None:
                 logging.warning(f"Module {self.module_name} has no 'spec' attribute")
                 logging.warning(f"Module {self.module_name} has no 'spec' attribute")
             return spec
             return spec
@@ -121,42 +156,31 @@ class ArchetypeTemplate:
         logging.warning(f"Spec for {self.module_name} has unexpected type: {type(spec)}")
         logging.warning(f"Spec for {self.module_name} has unexpected type: {type(spec)}")
         return None
         return None
 
 
-    def _merge_extension_file(self, spec_dict: OrderedDict) -> None:
-        """Merge extension.yaml variables into spec_dict."""
-        extension_file = self.template_dir / "extension.yaml"
-        if not extension_file.exists():
-            return
-
+    def _merge_archetype_vars(self, spec_dict: OrderedDict, archetype_vars: dict) -> None:
+        """Merge variables from archetypes.yaml into spec_dict."""
         try:
         try:
-            with extension_file.open() as f:
-                extension_vars = yaml.safe_load(f)
-
-            if not extension_vars:
-                return
-
-            applied_count, new_vars = self._apply_extension_vars(spec_dict, extension_vars)
+            applied_count, new_vars = self._apply_archetype_vars(spec_dict, archetype_vars)
             self._add_testing_section(spec_dict, new_vars)
             self._add_testing_section(spec_dict, new_vars)
-
+            
             logging.debug(
             logging.debug(
-                f"Applied {applied_count} extension defaults, "
-                f"added {len(new_vars)} new test variables from {extension_file}"
+                f"Applied {applied_count} archetype var overrides, "
+                f"added {len(new_vars)} new test variables"
             )
             )
         except Exception as e:
         except Exception as e:
-            logging.warning(f"Failed to load extension.yaml: {e}")
+            logging.warning(f"Failed to merge archetype vars: {e}")
 
 
-    def _apply_extension_vars(self, spec_dict: OrderedDict, extension_vars: dict) -> tuple[int, dict]:
-        """Apply extension variables to existing spec sections."""
+    def _apply_archetype_vars(self, spec_dict: OrderedDict, archetype_vars: dict) -> tuple[int, dict]:
+        """Apply archetype variables to existing spec sections or collect as new variables."""
         applied_count = 0
         applied_count = 0
         new_vars = {}
         new_vars = {}
 
 
-        for var_name, var_spec in extension_vars.items():
+        for var_name, var_spec in archetype_vars.items():
             if self._update_existing_var(spec_dict, var_name, var_spec):
             if self._update_existing_var(spec_dict, var_name, var_spec):
                 applied_count += 1
                 applied_count += 1
             else:
             else:
                 new_vars[var_name] = var_spec
                 new_vars[var_name] = var_spec
 
 
         return applied_count, new_vars
         return applied_count, new_vars
-
     def _update_existing_var(self, spec_dict: OrderedDict, var_name: str, var_spec: dict) -> bool:
     def _update_existing_var(self, spec_dict: OrderedDict, var_name: str, var_spec: dict) -> bool:
         """Update existing variable with extension default."""
         """Update existing variable with extension default."""
         if "default" not in var_spec:
         if "default" not in var_spec:
@@ -304,19 +328,16 @@ def _display_archetype_content(archetype_path: Path) -> None:
     console.print()
     console.print()
 
 
 
 
-def _parse_var_overrides(var: list[str] | None) -> dict[str, str]:
-    """Parse --var options into a dictionary."""
-    variables = {}
+def _parse_var_overrides(var: list[str] | None) -> dict[str, Any]:
+    """Parse --var options into a dictionary with type conversion.
+    
+    Uses the CLI's parse_var_inputs function to ensure consistent behavior.
+    """
     if not var:
     if not var:
-        return variables
-
-    for var_option in var:
-        if "=" in var_option:
-            key, value = var_option.split("=", 1)
-            variables[key] = value
-        else:
-            console.print(f"[yellow]Warning: Invalid --var format '{var_option}' (use KEY=VALUE)[/yellow]")
-    return variables
+        return {}
+    
+    # Use CLI's parse_var_inputs function (no extra_args for archetypes)
+    return parse_var_inputs(var, [])
 
 
 
 
 def _display_generated_preview(output_dir: Path, rendered_files: dict[str, str]) -> None:
 def _display_generated_preview(output_dir: Path, rendered_files: dict[str, str]) -> None:
@@ -343,7 +364,7 @@ def create_module_commands(module_name: str) -> Typer:
         archetypes = find_archetypes(module_name)
         archetypes = find_archetypes(module_name)
 
 
         if not archetypes:
         if not archetypes:
-            display.display_warning(
+            display.warning(
                 f"No archetypes found for module '{module_name}'",
                 f"No archetypes found for module '{module_name}'",
                 context=f"directory: {ARCHETYPES_DIR / module_name}",
                 context=f"directory: {ARCHETYPES_DIR / module_name}",
             )
             )
@@ -362,25 +383,23 @@ def create_module_commands(module_name: str) -> Typer:
         archetype_path = _find_archetype_by_id(archetypes, id)
         archetype_path = _find_archetype_by_id(archetypes, id)
 
 
         if not archetype_path:
         if not archetype_path:
-            display.display_error(f"Archetype '{id}' not found", context=f"module '{module_name}'")
+            display.error(f"Archetype '{id}' not found", context=f"module '{module_name}'")
             return
             return
 
 
-        archetype = ArchetypeTemplate(archetype_path, module_name)
-        _display_archetype_details(archetype, module_name)
         _display_archetype_content(archetype_path)
         _display_archetype_content(archetype_path)
 
 
     @module_app.command()
     @module_app.command()
     def generate(
     def generate(
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
         directory: str | None = Argument(None, help="Output directory (for reference only - no files are written)"),
         directory: str | None = Argument(None, help="Output directory (for reference only - no files are written)"),
-        var: builtins.list[str] | None = None,
+        var: builtins.list[str] | None = Option(None, "--var", "-v", help="Set variable (KEY=VALUE format, can be used multiple times)"),
     ) -> None:
     ) -> None:
         """Generate output from an archetype file (always in preview mode)."""
         """Generate output from an archetype file (always in preview mode)."""
         archetypes = find_archetypes(module_name)
         archetypes = find_archetypes(module_name)
         archetype_path = _find_archetype_by_id(archetypes, id)
         archetype_path = _find_archetype_by_id(archetypes, id)
 
 
         if not archetype_path:
         if not archetype_path:
-            display.display_error(f"Archetype '{id}' not found", context=f"module '{module_name}'")
+            display.error(f"Archetype '{id}' not found", context=f"module '{module_name}'")
             return
             return
 
 
         archetype = ArchetypeTemplate(archetype_path, module_name)
         archetype = ArchetypeTemplate(archetype_path, module_name)
@@ -389,12 +408,12 @@ def create_module_commands(module_name: str) -> Typer:
         try:
         try:
             rendered_files = archetype.render(variables)
             rendered_files = archetype.render(variables)
         except Exception as e:
         except Exception as e:
-            display.display_error(f"Failed to render archetype: {e}", context=f"archetype '{id}'")
+            display.error(f"Failed to render archetype: {e}", context=f"archetype '{id}'")
             return
             return
 
 
         output_dir = Path(directory) if directory else Path.cwd()
         output_dir = Path(directory) if directory else Path.cwd()
         _display_generated_preview(output_dir, rendered_files)
         _display_generated_preview(output_dir, rendered_files)
-        display.display_success("Preview complete - no files were written")
+        display.success("Preview complete - no files were written")
 
 
     return module_app
     return module_app
 
 

+ 38 - 0
archetypes/compose/archetypes.yaml

@@ -0,0 +1,38 @@
+---
+# Archetypes configuration for compose module
+
+# Schema version to use for loading module spec
+schema: "1.2"
+
+# Variable overrides and additional testing variables
+vars:
+  # Override defaults for testing
+  service_name:
+    default: testapp
+  container_name:
+    default: testapp-container
+  container_hostname:
+    default: testapp-host
+  traefik_host:
+    default: testapp.home.arpa
+  
+  # Additional variables not in module spec
+  service_image:
+    type: str
+    default: testapp:latest
+    description: Docker image for the service
+  
+  service_port:
+    type: int
+    default: 80
+    description: Internal container port
+  
+  volume_name:
+    type: str
+    default: testapp-data
+    description: Volume name for persistent storage
+  
+  traefik_middleware:
+    type: str
+    default: none
+    description: Traefik middleware chain (comma-separated)

+ 19 - 0
archetypes/compose/compose.yaml.j2

@@ -0,0 +1,19 @@
+---
+{% include 'service-v1.j2' %}
+{% include 'service-environment-v1.j2' %}
+{% include 'service-networks-v1.j2' %}
+{% include 'service-ports-v1.j2' %}
+{% include 'service-volumes-v2.j2' %}
+{% include 'service-resources-v1.j2' %}
+{% include 'service-configs-v1.j2' %}
+{% include 'service-secrets-v1.j2' %}
+{% include 'service-labels-v1.j2' %}
+{% include 'service-deploy-v1.j2' %}
+
+{% include 'volumes-v2.j2' %}
+
+{% include 'networks-v1.j2' %}
+
+{% include 'configs-v1.j2' %}
+
+{% include 'secrets-v1.j2' %}

+ 2 - 17
archetypes/compose/configs-v1.j2

@@ -1,20 +1,5 @@
-{#
-  Archetype: toplevel-configs-v1
-  
-  Description:
-    Swarm configs definition from file source.
-  
-  Approach:
-    - Only applies to swarm mode
-    - Reads config from file at deploy time
-    - Configs are immutable once created
-  
-  Usage:
-    Use with service-configs-v1 for configuration file management.
-    Create configuration file before deploying stack.
-#}
 {% if swarm_enabled %}
 {% if swarm_enabled %}
 configs:
 configs:
-  {{ config_name }}:
-    file: ./config/app.yaml
+  {{ service_name }}_config_1:
+    file: ./config/testapp.yaml
 {% endif %}
 {% endif %}

+ 0 - 134
archetypes/compose/extension.yaml

@@ -1,134 +0,0 @@
----
-# Extension variables for archetype testing
-# These variables provide defaults for variables that have no default in the module spec
-# or add custom variables specifically needed for archetype testing
-
-# Variables from spec that need defaults for testing
-service_name:
-  type: str
-  description: Service name for testing
-  default: testapp
-
-container_name:
-  type: str
-  description: Container name for testing
-  default: testapp-container
-
-container_hostname:
-  type: str
-  description: Container hostname for testing
-  default: testapp-host
-
-traefik_host:
-  type: hostname
-  description: Traefik host for testing
-  default: app.example.com
-
-database_port:
-  type: int
-  description: Database port for testing
-  default: 5432
-
-database_name:
-  type: str
-  description: Database name for testing
-  default: testdb
-
-database_user:
-  type: str
-  description: Database user for testing
-  default: dbuser
-
-database_password:
-  type: str
-  description: Database password for testing
-  default: secretpassword123
-  sensitive: true
-
-email_host:
-  type: str
-  description: Email server host for testing
-  default: smtp.example.com
-
-email_username:
-  type: str
-  description: Email username for testing
-  default: noreply@example.com
-
-email_password:
-  type: str
-  description: Email password for testing
-  default: emailpass123
-  sensitive: true
-
-email_from:
-  type: str
-  description: Email from address for testing
-  default: noreply@example.com
-
-authentik_url:
-  type: url
-  description: Authentik URL for testing
-  default: https://auth.example.com
-
-authentik_slug:
-  type: str
-  description: Authentik application slug for testing
-  default: testapp
-
-authentik_client_id:
-  type: str
-  description: Authentik client ID for testing
-  default: client_id_12345
-
-authentik_client_secret:
-  type: str
-  description: Authentik client secret for testing
-  default: client_secret_abcdef
-  sensitive: true
-
-# Custom variables specific to archetype testing (not in module spec)
-network_enabled:
-  type: bool
-  description: Enable network configuration for testing
-  default: true
-
-volume_external:
-  type: bool
-  description: Use external volume for testing
-  default: false
-
-ports_http:
-  type: int
-  description: HTTP port for testing
-  default: 8080
-
-secret_name:
-  type: str
-  description: Secret name for testing
-  default: app_secret
-
-config_name:
-  type: str
-  description: Config name for testing
-  default: app_config
-
-service_image:
-  type: str
-  description: Service image for testing
-  default: nginx:alpine
-
-service_port:
-  type: int
-  description: Service port for testing
-  default: 8080
-
-volume_name:
-  type: str
-  description: Volume name for testing
-  default: app_data
-
-traefik_middleware:
-  type: str
-  description: Traefik middleware for testing
-  default: auth@file

+ 0 - 25
archetypes/compose/networks-v1.j2

@@ -1,27 +1,4 @@
-{#
-  Archetype: networks-v1
-  
-  Description:
-    Consolidated top-level networks section supporting multiple modes:
-    - Bridge: Simple bridge network for standalone deployments
-    - External: Reference pre-existing networks
-    - Macvlan: L2 network access with static IP assignment
-    - Swarm: Overlay networks for multi-node swarm clusters
-  
-  Approach:
-    - Conditionally creates network based on network_mode or network_enabled
-    - Supports external networks (network_external flag)
-    - Macvlan includes IPAM configuration
-    - Swarm mode uses overlay driver with attachable option
-    - Always includes Traefik network as external when enabled
-  
-  Usage:
-    Use as the single networks archetype for all deployment types.
-    Adapts based on network_mode, swarm_enabled, and network_external variables.
-#}
-{% if network_enabled or traefik_enabled %}
 networks:
 networks:
-  {% if network_enabled %}
   {{ network_name }}:
   {{ network_name }}:
     {% if network_external %}
     {% if network_external %}
     external: true
     external: true
@@ -42,9 +19,7 @@ networks:
     driver: bridge
     driver: bridge
     {% endif %}
     {% endif %}
     {% endif %}
     {% endif %}
-  {% endif %}
   {% if traefik_enabled %}
   {% if traefik_enabled %}
   {{ traefik_network }}:
   {{ traefik_network }}:
     external: true
     external: true
   {% endif %}
   {% endif %}
-{% endif %}

+ 1 - 16
archetypes/compose/secrets-v1.j2

@@ -1,20 +1,5 @@
-{#
-  Archetype: toplevel-secrets-v1
-  
-  Description:
-    Swarm secrets definition from file source.
-  
-  Approach:
-    - Only applies to swarm mode
-    - Reads secret from file at deploy time
-    - Secrets are encrypted in swarm
-  
-  Usage:
-    Use with service-secrets-v1 for secure credential management.
-    Create .env.secret file containing the secret value.
-#}
 {% if swarm_enabled %}
 {% if swarm_enabled %}
 secrets:
 secrets:
-  {{ secret_name }}:
+  {{ service_name }}_secret_1:
     file: ./.env.secret
     file: ./.env.secret
 {% endif %}
 {% endif %}

+ 1 - 16
archetypes/compose/service-configs-v1.j2

@@ -1,20 +1,5 @@
-{#
-  Archetype: service-configs-v1
-  
-  Description:
-    Swarm configs reference for configuration files.
-  
-  Approach:
-    - Only applies to swarm mode
-    - References configs defined in top-level configs section
-    - Configs mounted at specified target path
-  
-  Usage:
-    Use for application configuration files in swarm.
-    Requires corresponding toplevel-configs-v1 archetype.
-#}
     {% if swarm_enabled %}
     {% if swarm_enabled %}
     configs:
     configs:
-      - source: {{ config_name }}
+      - source: {{ service_name }}_config_1
         target: /etc/app/config.yaml
         target: /etc/app/config.yaml
     {% endif %}
     {% endif %}

+ 3 - 16
archetypes/compose/service-deploy-v1.j2

@@ -1,19 +1,4 @@
-{#
-  Archetype: service-deploy-traefik-v1
-  
-  Description:
-    Swarm deployment with Traefik labels in deploy section.
-  
-  Approach:
-    - Labels must be in deploy section for swarm mode
-    - Includes full HTTP + HTTPS Traefik configuration
-    - Critical: traefik.docker.network label for multi-network containers
-  
-  Usage:
-    Use for swarm services exposed through Traefik.
-    Combines with service-labels-traefik-https-v1 for standalone mode.
-#}
-    {% if swarm_enabled and traefik_enabled %}
+    {% if swarm_enabled %}
     deploy:
     deploy:
       mode: {{ swarm_placement_mode }}
       mode: {{ swarm_placement_mode }}
       {% if swarm_placement_mode == 'replicated' %}
       {% if swarm_placement_mode == 'replicated' %}
@@ -21,6 +6,7 @@
       {% endif %}
       {% endif %}
       restart_policy:
       restart_policy:
         condition: on-failure
         condition: on-failure
+      {% if traefik_enabled %}
       labels:
       labels:
         - traefik.enable=true
         - traefik.enable=true
         - traefik.docker.network={{ traefik_network }}
         - traefik.docker.network={{ traefik_network }}
@@ -35,4 +21,5 @@
         - traefik.http.routers.{{ service_name }}-https.tls=true
         - traefik.http.routers.{{ service_name }}-https.tls=true
         - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
         - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
         {% endif %}
         {% endif %}
+      {% endif %}
     {% endif %}
     {% endif %}

+ 1 - 17
archetypes/compose/service-environment-v1.j2

@@ -1,25 +1,9 @@
-{#
-  Archetype: service-environment-v1
-  
-  Description:
-    Environment variables for common container configurations.
-  
-  Approach:
-    - Sets standard environment variables (timezone, UID/GID)
-    - Demonstrates secret handling: file-based for swarm, env var for standalone
-    - Uses user_uid/user_gid from module spec general section
-  
-  Usage:
-    Use for services that need timezone and user/group configuration.
-    Adapt the secret handling pattern for your specific secret variables.
-    Replace SECRET example with actual secret variable names as needed.
-#}
     environment:
     environment:
       - TZ={{ container_timezone }}
       - TZ={{ container_timezone }}
       - UID={{ user_uid }}
       - UID={{ user_uid }}
       - GID={{ user_gid }}
       - GID={{ user_gid }}
       {% if swarm_enabled %}
       {% if swarm_enabled %}
-      - SECRET=/run/secrets/{{ secret_name }}
+      - SECRET=/run/secrets/{{ service_name }}_secret_1
       {% else %}
       {% else %}
       - SECRET=${SECRET}
       - SECRET=${SECRET}
       {% endif %}
       {% endif %}

+ 1 - 16
archetypes/compose/service-labels-v1.j2

@@ -1,19 +1,4 @@
-{#
-  Archetype: service-labels-traefik-middleware-v1
-  
-  Description:
-    Traefik labels with middleware support for authentication, headers, etc.
-  
-  Approach:
-    - Extends HTTPS configuration with middleware assignment
-    - Middlewares applied to both HTTP and HTTPS routers
-    - Supports chaining multiple middlewares (comma-separated)
-  
-  Usage:
-    Use when you need authentication, rate limiting, headers, or other
-    Traefik middleware features. Define middlewares in Traefik config or labels.
-#}
-    {% if traefik_enabled %}
+    {% if traefik_enabled and not swarm_enabled %}
     labels:
     labels:
       - traefik.enable=true
       - traefik.enable=true
       - traefik.docker.network={{ traefik_network }}
       - traefik.docker.network={{ traefik_network }}

+ 0 - 16
archetypes/compose/service-networks-v1.j2

@@ -1,19 +1,3 @@
-{#
-  Archetype: service-networks-macvlan-v1
-  
-  Description:
-    Network configuration supporting host, bridge, and macvlan modes.
-  
-  Approach:
-    - Host mode: Uses network_mode: host (no networks section)
-    - Macvlan mode: Assigns static IP address
-    - Bridge mode: Simple network attachment
-    - Always includes Traefik network if enabled
-  
-  Usage:
-    Use for services that need specific network modes (e.g., Pi-hole with macvlan).
-    Requires network_mode variable ('host', 'bridge', or 'macvlan').
-#}
     {% if network_mode == 'host' %}
     {% if network_mode == 'host' %}
     network_mode: host
     network_mode: host
     {% else %}
     {% else %}

+ 2 - 17
archetypes/compose/service-ports-v1.j2

@@ -1,26 +1,11 @@
-{#
-  Archetype: service-ports-conditional-v1
-  
-  Description:
-    Port mappings that are only exposed when Traefik is disabled.
-  
-  Approach:
-    - Swarm mode: Uses long syntax with mode:host for proper host binding
-    - Standalone mode: Uses short syntax for simplicity
-    - Conditionally skipped if Traefik handles routing
-  
-  Usage:
-    Use for HTTP/HTTPS services that can be proxied through Traefik.
-    Ports are only exposed directly when traefik_enabled=false.
-#}
     {% if not traefik_enabled %}
     {% if not traefik_enabled %}
     ports:
     ports:
       {% if swarm_enabled %}
       {% if swarm_enabled %}
-      - target: {{ service_port }}
+      - target: 80
         published: {{ ports_http }}
         published: {{ ports_http }}
         protocol: tcp
         protocol: tcp
         mode: host
         mode: host
       {% else %}
       {% else %}
-      - "{{ ports_http }}:{{ service_port }}"
+      - "{{ ports_http }}:80"
       {% endif %}
       {% endif %}
     {% endif %}
     {% endif %}

+ 0 - 16
archetypes/compose/service-resources-v1.j2

@@ -1,19 +1,3 @@
-{#
-  Archetype: service-resources-v1
-  
-  Description:
-    Resource limits (CPU/memory) for production deployments (schema 1.2+).
-  
-  Approach:
-    - Standalone mode: Uses deploy.resources (Compose spec v3+)
-    - Swarm mode: Includes reservations for resource guarantees
-    - CPU limits specified as decimal (0.5, 1.0, 2.0)
-    - Memory limits with units (512M, 1G, 2G)
-  
-  Usage:
-    Use for production services that need resource constraints.
-    Prevents resource exhaustion and enables better capacity planning.
-#}
     {% if resources_enabled %}
     {% if resources_enabled %}
     deploy:
     deploy:
       resources:
       resources:

+ 1 - 17
archetypes/compose/service-secrets-v1.j2

@@ -1,20 +1,4 @@
-{#
-  Archetype: service-secrets-v1
-  
-  Description:
-    Swarm secrets reference for sensitive data.
-  
-  Approach:
-    - Only applies to swarm mode
-    - References secrets defined in top-level secrets section
-    - Secrets mounted at /run/secrets/<secret_name>
-  
-  Usage:
-    Use for passwords, API keys, certificates in swarm.
-    Requires corresponding secrets-v1 (top-level) archetype.
-    Must be used within a service definition.
-#}
 {% if swarm_enabled %}
 {% if swarm_enabled %}
     secrets:
     secrets:
-      - {{ secret_name }}
+      - {{ service_name }}_secret_1
 {% endif %}
 {% endif %}

+ 1 - 16
archetypes/compose/service-v1.j2

@@ -1,21 +1,6 @@
-{#
-  Archetype: service-basic-v1
-  
-  Description:
-    Basic service definition with image, container name (non-swarm), and hostname.
-    This is the foundation for any Docker Compose service.
-  
-  Approach:
-    - Defines the service name and image
-    - Conditionally adds container_name only for non-swarm deployments
-    - Sets hostname for service identification
-  
-  Usage:
-    Use this as the starting point for any service definition.
-#}
 services:
 services:
   {{ service_name }}:
   {{ service_name }}:
-    image: {{ service_image }}
+    image: testapp:latest
     {% if not swarm_enabled %}
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     restart: {{ restart_policy }}
     container_name: {{ container_name }}
     container_name: {{ container_name }}

+ 0 - 26
archetypes/compose/service-volumes-v1.j2

@@ -1,26 +0,0 @@
-{#
-  Archetype: service-volumes-v1
-  
-  Description:
-    Service volume mounts supporting standalone and swarm modes.
-  
-  Approach:
-    - Standalone mode: Uses named volumes
-    - Swarm mount mode: Uses bind mounts from swarm_volume_mount_path
-    - Swarm local/nfs mode: Uses named volumes
-  
-  Usage:
-    Use for services that need persistent storage.
-    Follows the pattern from pihole template.
-    Uses volume_name variable for named volumes.
-#}
-    volumes:
-      {% if not swarm_enabled %}
-      - {{ volume_name }}:/data
-      {% else %}
-      {% if swarm_volume_mode == 'mount' %}
-      - {{ swarm_volume_mount_path }}/data:/data:rw
-      {% elif swarm_volume_mode in ['local', 'nfs'] %}
-      - {{ volume_name }}:/data
-      {% endif %}
-      {% endif %}

+ 8 - 17
archetypes/compose/service-volumes-v2.j2

@@ -1,22 +1,13 @@
-{#
-  Archetype: service-volumes-v2
-  
-  Description:
-    Service volume mounts using dedicated volume section (schema 1.2+).
-  
-  Approach:
-    - Uses volume_mode instead of swarm_volume_mode
-    - Standalone mode: Uses named volumes
-    - Mount mode: Uses bind mounts from volume_mount_path
-    - NFS mode: Uses named volumes with NFS driver
-  
-  Usage:
-    Use for schema 1.2+ templates that need persistent storage.
-    The volume section works for both standalone and swarm modes.
-#}
     volumes:
     volumes:
       {% if volume_mode == 'mount' %}
       {% if volume_mode == 'mount' %}
       - {{ volume_mount_path }}/data:/data:rw
       - {{ volume_mount_path }}/data:/data:rw
       {% else %}
       {% else %}
-      - {{ volume_name }}:/data
+      - testapp_volume:/data
+      {% endif %}
+      {% if not swarm_enabled %}
+      {% if volume_mode == 'mount' %}
+      - {{ volume_mount_path }}/config/testapp.yaml:/etc/app/config.yaml:ro
+      {% else %}
+      - ./config/testapp.yaml:/etc/app/config.yaml:ro
+      {% endif %}
       {% endif %}
       {% endif %}

+ 0 - 40
archetypes/compose/volumes-v1.j2

@@ -1,40 +0,0 @@
-{#
-  Archetype: volumes-v1
-  
-  Description:
-    Consolidated top-level volumes section supporting multiple modes:
-    - Simple: Basic local volumes for standalone deployments
-    - External: Reference pre-existing volumes
-    - NFS: Network filesystem for shared storage in swarm
-    - Swarm: Flexible mode supporting mount/local/NFS strategies
-  
-  Approach:
-    - External volumes: No definition needed (external: true not used at top-level)
-    - Standalone mode: Always uses local volumes
-    - Swarm mode with mount: No volume definition (uses bind mounts)
-    - Swarm mode with local: Simple local volumes
-    - Swarm mode with NFS: Network filesystem with driver options
-  
-  Usage:
-    Use as the single volumes archetype for all deployment types.
-    Adapts based on volume_external, swarm_enabled, and swarm_volume_mode variables.
-#}
-{% if not volume_external %}
-{% if swarm_enabled %}
-{% if swarm_volume_mode in ['local', 'nfs'] %}
-volumes:
-  {{ volume_name }}:
-    {% if swarm_volume_mode == 'nfs' %}
-    driver: local
-    driver_opts:
-      type: nfs
-      o: addr={{ swarm_volume_nfs_server }},{{ swarm_volume_nfs_options }}
-      device: ":{{ swarm_volume_nfs_path }}"
-    {% endif %}
-{% endif %}
-{% else %}
-volumes:
-  {{ volume_name }}:
-    driver: local
-{% endif %}
-{% endif %}

+ 2 - 18
archetypes/compose/volumes-v2.j2

@@ -1,26 +1,10 @@
-{#
-  Archetype: volumes-v2
-  
-  Description:
-    Top-level volumes section using dedicated volume section (schema 1.2+).
-  
-  Approach:
-    - Uses volume_mode instead of swarm_volume_mode
-    - Mount mode: No volume definition (uses bind mounts)
-    - Local mode: Simple local volumes
-    - NFS mode: Network filesystem with driver options
-  
-  Usage:
-    Use for schema 1.2+ templates with the new volume section.
-    Works universally for both standalone and swarm modes.
-#}
 {% if volume_mode == 'local' %}
 {% if volume_mode == 'local' %}
 volumes:
 volumes:
-  {{ volume_name }}:
+  testapp_volume:
     driver: local
     driver: local
 {% elif volume_mode == 'nfs' %}
 {% elif volume_mode == 'nfs' %}
 volumes:
 volumes:
-  {{ volume_name }}:
+  testapp_volume:
     driver: local
     driver: local
     driver_opts:
     driver_opts:
       type: nfs
       type: nfs

+ 42 - 4
cli/core/module/helpers.py

@@ -17,18 +17,24 @@ logger = logging.getLogger(__name__)
 
 
 
 
 def parse_var_inputs(var_options: list[str], extra_args: list[str]) -> dict[str, Any]:
 def parse_var_inputs(var_options: list[str], extra_args: list[str]) -> dict[str, Any]:
-    """Parse variable inputs from --var options and extra args.
+    """Parse variable inputs from --var options and extra args with type conversion.
 
 
     Supports formats:
     Supports formats:
       --var KEY=VALUE
       --var KEY=VALUE
       --var KEY VALUE
       --var KEY VALUE
 
 
+    Values are automatically converted to appropriate types:
+      - 'true', 'yes', '1' → True
+      - 'false', 'no', '0' → False
+      - Numeric strings → int or float
+      - Everything else → string
+
     Args:
     Args:
       var_options: List of variable options from CLI
       var_options: List of variable options from CLI
       extra_args: Additional arguments that may contain values
       extra_args: Additional arguments that may contain values
 
 
     Returns:
     Returns:
-      Dictionary of parsed variables
+      Dictionary of parsed variables with converted types
     """
     """
     variables = {}
     variables = {}
 
 
@@ -36,16 +42,48 @@ def parse_var_inputs(var_options: list[str], extra_args: list[str]) -> dict[str,
     for var_option in var_options:
     for var_option in var_options:
         if "=" in var_option:
         if "=" in var_option:
             key, value = var_option.split("=", 1)
             key, value = var_option.split("=", 1)
-            variables[key] = value
+            variables[key] = _convert_string_to_type(value)
         # --var KEY VALUE format - value should be in extra_args
         # --var KEY VALUE format - value should be in extra_args
         elif extra_args:
         elif extra_args:
-            variables[var_option] = extra_args.pop(0)
+            value = extra_args.pop(0)
+            variables[var_option] = _convert_string_to_type(value)
         else:
         else:
             logger.warning(f"No value provided for variable '{var_option}'")
             logger.warning(f"No value provided for variable '{var_option}'")
 
 
     return variables
     return variables
 
 
 
 
+def _convert_string_to_type(value: str) -> Any:
+    """Convert string value to appropriate Python type.
+    
+    Args:
+        value: String value to convert
+    
+    Returns:
+        Converted value (bool, int, float, or str)
+    """
+    # Boolean conversion
+    if value.lower() in ('true', 'yes', '1'):
+        return True
+    if value.lower() in ('false', 'no', '0'):
+        return False
+    
+    # Integer conversion
+    try:
+        return int(value)
+    except ValueError:
+        pass
+    
+    # Float conversion
+    try:
+        return float(value)
+    except ValueError:
+        pass
+    
+    # Return as string
+    return value
+
+
 def load_var_file(var_file_path: str) -> dict:
 def load_var_file(var_file_path: str) -> dict:
     """Load variables from a YAML file.
     """Load variables from a YAML file.
 
 

+ 5 - 0
cli/modules/compose/spec_v1_2.py

@@ -212,6 +212,11 @@ spec = OrderedDict(
             "toggle": "resources_enabled",
             "toggle": "resources_enabled",
             "description": "Set CPU and memory limits for the service.",
             "description": "Set CPU and memory limits for the service.",
             "vars": {
             "vars": {
+                "resources_enabled": {
+                    "description": "Enable resource limits",
+                    "type": "bool",
+                    "default": False,
+                },
                 "resources_cpu_limit": {
                 "resources_cpu_limit": {
                     "description": "Maximum CPU cores (e.g., 0.5, 1.0, 2.0)",
                     "description": "Maximum CPU cores (e.g., 0.5, 1.0, 2.0)",
                     "type": "str",
                     "type": "str",