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

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 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
 
-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
 
 **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
 - 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
 
 ### Added

+ 67 - 48
archetypes/__main__.py

@@ -9,6 +9,7 @@ from __future__ import annotations
 import builtins
 import importlib
 import logging
+import os
 import sys
 from collections import OrderedDict
 from pathlib import Path
@@ -17,8 +18,11 @@ from typing import Any
 import click
 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
-from cli.core.collection import VariableCollection
 from jinja2 import Environment, FileSystemLoader, StrictUndefined
 from rich.console import Console
 from rich.panel import Panel
@@ -29,6 +33,8 @@ from cli.core.display import DisplayManager
 from cli.core.exceptions import (
     TemplateRenderError,
 )
+from cli.core.module.helpers import parse_var_inputs
+from cli.core.template.variable_collection import VariableCollection
 
 app = Typer(
     help="Test and develop template snippets (archetypes) without full template structure.",
@@ -82,9 +88,14 @@ class ArchetypeTemplate:
         self.variables = self._load_module_spec()
 
     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:
-            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:
                 return None
 
@@ -92,18 +103,42 @@ class ArchetypeTemplate:
             if spec_dict is 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)
         except Exception as e:
             logging.warning(f"Could not load spec for module {self.module_name}: {e}")
             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}"
         try:
             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:
                 logging.warning(f"Module {self.module_name} has no 'spec' attribute")
             return spec
@@ -121,42 +156,31 @@ class ArchetypeTemplate:
         logging.warning(f"Spec for {self.module_name} has unexpected type: {type(spec)}")
         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:
-            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)
-
+            
             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:
-            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
         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):
                 applied_count += 1
             else:
                 new_vars[var_name] = var_spec
 
         return applied_count, new_vars
-
     def _update_existing_var(self, spec_dict: OrderedDict, var_name: str, var_spec: dict) -> bool:
         """Update existing variable with extension default."""
         if "default" not in var_spec:
@@ -304,19 +328,16 @@ def _display_archetype_content(archetype_path: Path) -> None:
     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:
-        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:
@@ -343,7 +364,7 @@ def create_module_commands(module_name: str) -> Typer:
         archetypes = find_archetypes(module_name)
 
         if not archetypes:
-            display.display_warning(
+            display.warning(
                 f"No archetypes found for module '{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)
 
         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
 
-        archetype = ArchetypeTemplate(archetype_path, module_name)
-        _display_archetype_details(archetype, module_name)
         _display_archetype_content(archetype_path)
 
     @module_app.command()
     def generate(
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
         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:
         """Generate output from an archetype file (always in preview mode)."""
         archetypes = find_archetypes(module_name)
         archetype_path = _find_archetype_by_id(archetypes, id)
 
         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
 
         archetype = ArchetypeTemplate(archetype_path, module_name)
@@ -389,12 +408,12 @@ def create_module_commands(module_name: str) -> Typer:
         try:
             rendered_files = archetype.render(variables)
         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
 
         output_dir = Path(directory) if directory else Path.cwd()
         _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
 

+ 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 %}
 configs:
-  {{ config_name }}:
-    file: ./config/app.yaml
+  {{ service_name }}_config_1:
+    file: ./config/testapp.yaml
 {% 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:
-  {% if network_enabled %}
   {{ network_name }}:
     {% if network_external %}
     external: true
@@ -42,9 +19,7 @@ networks:
     driver: bridge
     {% endif %}
     {% endif %}
-  {% endif %}
   {% if traefik_enabled %}
   {{ traefik_network }}:
     external: true
   {% 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 %}
 secrets:
-  {{ secret_name }}:
+  {{ service_name }}_secret_1:
     file: ./.env.secret
 {% 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 %}
     configs:
-      - source: {{ config_name }}
+      - source: {{ service_name }}_config_1
         target: /etc/app/config.yaml
     {% 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:
       mode: {{ swarm_placement_mode }}
       {% if swarm_placement_mode == 'replicated' %}
@@ -21,6 +6,7 @@
       {% endif %}
       restart_policy:
         condition: on-failure
+      {% if traefik_enabled %}
       labels:
         - traefik.enable=true
         - traefik.docker.network={{ traefik_network }}
@@ -35,4 +21,5 @@
         - traefik.http.routers.{{ service_name }}-https.tls=true
         - traefik.http.routers.{{ service_name }}-https.tls.certresolver={{ traefik_tls_certresolver }}
         {% 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:
       - TZ={{ container_timezone }}
       - UID={{ user_uid }}
       - GID={{ user_gid }}
       {% if swarm_enabled %}
-      - SECRET=/run/secrets/{{ secret_name }}
+      - SECRET=/run/secrets/{{ service_name }}_secret_1
       {% else %}
       - SECRET=${SECRET}
       {% 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:
       - traefik.enable=true
       - 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' %}
     network_mode: host
     {% 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 %}
     ports:
       {% if swarm_enabled %}
-      - target: {{ service_port }}
+      - target: 80
         published: {{ ports_http }}
         protocol: tcp
         mode: host
       {% else %}
-      - "{{ ports_http }}:{{ service_port }}"
+      - "{{ ports_http }}:80"
       {% 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 %}
     deploy:
       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 %}
     secrets:
-      - {{ secret_name }}
+      - {{ service_name }}_secret_1
 {% 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:
   {{ service_name }}:
-    image: {{ service_image }}
+    image: testapp:latest
     {% if not swarm_enabled %}
     restart: {{ restart_policy }}
     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:
       {% if volume_mode == 'mount' %}
       - {{ volume_mount_path }}/data:/data:rw
       {% 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 %}

+ 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' %}
 volumes:
-  {{ volume_name }}:
+  testapp_volume:
     driver: local
 {% elif volume_mode == 'nfs' %}
 volumes:
-  {{ volume_name }}:
+  testapp_volume:
     driver: local
     driver_opts:
       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]:
-    """Parse variable inputs from --var options and extra args.
+    """Parse variable inputs from --var options and extra args with type conversion.
 
     Supports formats:
       --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:
       var_options: List of variable options from CLI
       extra_args: Additional arguments that may contain values
 
     Returns:
-      Dictionary of parsed variables
+      Dictionary of parsed variables with converted types
     """
     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:
         if "=" in var_option:
             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
         elif extra_args:
-            variables[var_option] = extra_args.pop(0)
+            value = extra_args.pop(0)
+            variables[var_option] = _convert_string_to_type(value)
         else:
             logger.warning(f"No value provided for variable '{var_option}'")
 
     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:
     """Load variables from a YAML file.
 

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

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