# Templates

Jinja2 and Nickel templates for configuration and deployment generation.

## Purpose

Templates provide:
- **Nickel output generation** - Jinja2 templates for TypeDialog nickel-roundtrip
- **Docker Compose generation** - Infrastructure-as-code for containerized deployment
- **Kubernetes manifests** - Declarative deployment manifests
- **TOML export** - Service configuration generation for Rust codebase

## File Organization

```bash
templates/
├── README.md                           # This file
├── orchestrator-config.ncl.j2          # Nickel output template (Jinja2)
├── control-center-config.ncl.j2        # Nickel output template (Jinja2)
├── mcp-server-config.ncl.j2            # Nickel output template (Jinja2)
├── installer-config.ncl.j2             # Nickel output template (Jinja2)
├── docker-compose/                     # Docker Compose templates
│   ├── platform-stack.solo.yml.ncl
│   ├── platform-stack.multiuser.yml.ncl
│   ├── platform-stack.cicd.yml.ncl
│   └── platform-stack.enterprise.yml.ncl
├── kubernetes/                         # Kubernetes templates
│   ├── orchestrator-deployment.yaml.ncl
│   ├── orchestrator-service.yaml.ncl
│   ├── control-center-deployment.yaml.ncl
│   ├── control-center-service.yaml.ncl
│   └── platform-ingress.yaml.ncl
└── configs/                            # Service config templates (optional)
    ├── orchestrator-config.toml.ncl
    ├── control-center-config.toml.ncl
    └── mcp-server-config.toml.ncl
```

## Jinja2 Config Templates

**Critical for TypeDialog nickel-roundtrip workflow**:

```nickel
typedialog-web nickel-roundtrip "$CONFIG" "forms/{service}-form.toml" --output "$CONFIG" --template "templates/{service}-config.ncl.j2"
```

### Template Pattern: orchestrator-config.ncl.j2

```nickel
# Orchestrator Configuration - Nickel Format
# Auto-generated by provisioning TypeDialog
# Edit via: nu scripts/configure.nu orchestrator {mode}

{
  orchestrator = {
    # Workspace Configuration
    workspace = {
      {%- if workspace_name %}
      name = "{{ workspace_name }}",
      {%- endif %}
      {%- if workspace_path %}
      path = "{{ workspace_path }}",
      {%- endif %}
      {%- if workspace_enabled is defined %}
      enabled = {{ workspace_enabled | lower }},
      {%- endif %}
      {%- if multi_workspace is defined %}
      multi_workspace = {{ multi_workspace | lower }},
      {%- endif %}
    },

    # Server Configuration
    server = {
      {%- if server_host %}
      host = "{{ server_host }}",
      {%- endif %}
      {%- if server_port %}
      port = {{ server_port }},
      {%- endif %}
      {%- if server_workers %}
      workers = {{ server_workers }},
      {%- endif %}
      {%- if server_keep_alive %}
      keep_alive = {{ server_keep_alive }},
      {%- endif %}
    },

    # Storage Configuration
    storage = {
      {%- if storage_backend %}
      backend = '{{ storage_backend }},
      {%- endif %}
      {%- if storage_path %}
      path = "{{ storage_path }}",
      {%- endif %}
      {%- if surrealdb_url %}
      surrealdb_url = "{{ surrealdb_url }}",
      {%- endif %}
    },

    # Queue Configuration
    queue = {
      {%- if max_concurrent_tasks %}
      max_concurrent_tasks = {{ max_concurrent_tasks }},
      {%- endif %}
      {%- if retry_attempts %}
      retry_attempts = {{ retry_attempts }},
      {%- endif %}
      {%- if retry_delay %}
      retry_delay = {{ retry_delay }},
      {%- endif %}
      {%- if task_timeout %}
      task_timeout = {{ task_timeout }},
      {%- endif %}
    },

    # Monitoring Configuration (optional)
    {%- if enable_monitoring is defined and enable_monitoring %}
    monitoring = {
      enabled = true,
      {%- if metrics_interval %}
      metrics_interval = {{ metrics_interval }},
      {%- endif %}
      {%- if health_check_interval %}
      health_check_interval = {{ health_check_interval }},
      {%- endif %}
    },
    {%- endif %}
  },
}
```

### Key Jinja2 Patterns

**Conditional blocks** (only include if field is set):

```json
{%- if workspace_name %}
name = "{{ workspace_name }}",
{%- endif %}
```

**String values** (with quotes):

```json
{%- if storage_backend %}
backend = '{{ storage_backend }},  # Enum (atom syntax)
{%- endif %}
```

**Numeric values** (no quotes):

```json
{%- if server_port %}
port = {{ server_port }},  # Number
{%- endif %}
```

**Boolean values** (lower case):

```json
{%- if workspace_enabled is defined %}
enabled = {{ workspace_enabled | lower }},  # Boolean (true/false)
{%- endif %}
```

**Comments** (for generated files):

```bash
# Auto-generated by provisioning TypeDialog
# Edit via: nu scripts/configure.nu orchestrator {mode}
```

## Docker Compose Templates

Nickel templates that import from `values/*.ncl`:

```nickel
# templates/docker-compose/platform-stack.solo.yml.ncl
# Docker Compose Platform Stack - Solo Mode
# Imports config from values/orchestrator.solo.ncl

let orchestrator_config = import "../../values/orchestrator.solo.ncl" in
let control_center_config = import "../../values/control-center.solo.ncl" in

{
  version = "3.8",
  services = {
    orchestrator = {
      image = "provisioning-orchestrator:latest",
      container_name = "orchestrator",
      ports = [
        "%{std.to_string orchestrator_config.orchestrator.server.port}:9090",
      ],
      environment = {
        ORCHESTRATOR_SERVER_HOST = orchestrator_config.orchestrator.server.host,
        ORCHESTRATOR_SERVER_PORT = std.to_string orchestrator_config.orchestrator.server.port,
        ORCHESTRATOR_STORAGE_BACKEND = orchestrator_config.orchestrator.storage.backend,
      },
      volumes = [
        "./data/orchestrator:%{orchestrator_config.orchestrator.storage.path}",
      ],
      restart = "unless-stopped",
    },
    control-center = {
      image = "provisioning-control-center:latest",
      container_name = "control-center",
      ports = [
        "%{std.to_string control_center_config.control_center.server.port}:8080",
      ],
      environment = {
        CONTROL_CENTER_SERVER_HOST = control_center_config.control_center.server.host,
        CONTROL_CENTER_SERVER_PORT = std.to_string control_center_config.control_center.server.port,
      },
      restart = "unless-stopped",
    },
  },
}
```

### Rendering Docker Compose

```bash
# Export Nickel template to YAML
nickel export --format json templates/docker-compose/platform-stack.solo.yml.ncl | yq -P > docker-compose.solo.yml
```

## Kubernetes Templates

Nickel templates for Kubernetes manifests:

```nickel
# templates/kubernetes/orchestrator-deployment.yaml.ncl
let config = import "../../values/orchestrator.solo.ncl" in

{
  apiVersion = "apps/v1",
  kind = "Deployment",
  metadata = {
    name = "orchestrator",
    labels = {
      app = "orchestrator",
    },
  },
  spec = {
    replicas = 1,
    selector = {
      matchLabels = {
        app = "orchestrator",
      },
    },
    template = {
      metadata = {
        labels = {
          app = "orchestrator",
        },
      },
      spec = {
        containers = [
          {
            name = "orchestrator",
            image = "provisioning-orchestrator:latest",
            ports = [
              {
                containerPort = 9090,
              },
            ],
            env = [
              {
                name = "ORCHESTRATOR_SERVER_PORT",
                value = std.to_string config.orchestrator.server.port,
              },
              {
                name = "ORCHESTRATOR_STORAGE_BACKEND",
                value = config.orchestrator.storage.backend,
              },
            ],
            volumeMounts = [
              {
                name = "data",
                mountPath = config.orchestrator.storage.path,
              },
            ],
          },
        ],
        volumes = [
          {
            name = "data",
            persistentVolumeClaim = {
              claimName = "orchestrator-pvc",
            },
          },
        ],
      },
    },
  },
}
```

## Rendering Templates

### Render to JSON

```bash
nickel export --format json templates/orchestrator-config.ncl.j2 > config.json
```

### Render to YAML (via yq)

```yaml
nickel export --format json templates/kubernetes/orchestrator-deployment.yaml.ncl | yq -P > deployment.yaml
```

### Render to TOML

```toml
nickel export --format toml templates/configs/orchestrator-config.toml.ncl > config.toml
```

## Template Variables

Variables in templates come from:
1. **Form values** (TypeDialog input)
2. **Imported configs** (Nickel imports)
3. **Constraint interpolation** (constraints.toml)

## Best Practices

1. **Use conditional blocks** - Only include fields if set
2. **Import configs** - Reuse Nickel configs in templates
3. **Type conversion** - Use `std.to_string` for numeric values
4. **Comments** - Explain generated/auto-edited markers
5. **Validation** - Use `nickel typecheck` to verify templates
6. **Environment variables** - Prefer env over hardcoding

## Template Testing

```bash
# Typecheck Jinja2 + Nickel template
nickel typecheck templates/orchestrator-config.ncl.j2

# Evaluate and view output
nickel eval templates/orchestrator-config.ncl.j2

# Export and validate output
nickel export --format json templates/orchestrator-config.ncl.j2 | jq '.'
```

## Adding a New Template

1. **Create template file** (`{service}-config.ncl.j2` or `{name}.yml.ncl`)
2. **Define structure** (Nickel or Jinja2)
3. **Import configs** (if Nickel)
4. **Use variables** (from forms or imports)
5. **Typecheck**: `nickel typecheck templates/{file}`
6. **Test rendering**: `nickel export {format} templates/{file}`

---

**Version**: 1.0.0
**Last Updated**: 2025-01-05
