Jesús Pérez 6a59d34bb1
chore: update provisioning configuration and documentation
Update configuration files, templates, and internal documentation
for the provisioning repository system.

Configuration Updates:
- KMS configuration modernization
- Plugin system settings
- Service port mappings
- Test cluster topologies
- Installation configuration examples
- VM configuration defaults
- Cedar authorization policies

Documentation Updates:
- Library module documentation
- Extension API guides
- AI system documentation
- Service management guides
- Test environment setup
- Plugin usage guides
- Validator configuration documentation

All changes are backward compatible.
2025-12-11 21:50:42 +00:00

12 KiB

Configuration Templates

Purpose: Template files for generating workspace configurations

Template Extension Conventions

This project uses TWO template extensions for different purposes:

.template Extension (This Directory)

  • Purpose: Workspace initialization only
  • Engine: Simple string substitution ({{variable}})
  • Usage: One-time generation during workspace creation
  • Dependency: None (no plugins required)
  • Complexity: Low (no loops/conditionals needed)

Example:

workspace:
  name: "{{workspace.name}}"
  created: "{{now.iso}}"

When to use:

  • Workspace initialization templates
  • One-time setup files
  • No dynamic logic needed

.j2 Extension (Rest of Codebase)

  • Purpose: Runtime configuration generation
  • Engine: Jinja2 (via nu_plugin_tera)
  • Usage: Dynamic config rendering during operations
  • Dependency: Requires nu_plugin_tera plugin
  • Complexity: High (conditionals, loops, filters)

Example:

{%- if taskserv.mode == "ha" %}
REPLICAS={{taskserv.replicas}}
{%- endif %}

{% for node in cluster.nodes -%}
NODE_{{loop.index}}={{node.hostname}}
{% endfor %}

When to use:

  • Runtime configuration generation
  • Dynamic values from environment
  • Complex logic (conditionals, loops)

Why Two Extensions?

  1. Separation of Concerns: Init and runtime are fundamentally different operations

    • Init happens once during workspace creation
    • Runtime happens continuously during operations
  2. No Plugin Dependency for Init: Workspace creation works without external plugins

    • Simple string replacement is sufficient for initialization
    • nu_plugin_tera is only needed for runtime rendering
    • Initialization is more portable and reliable
  3. Semantic Clarity: Extension signals the purpose immediately

    • Developers see .template and know: "This is for initialization"
    • Developers see .j2 and know: "This is for runtime rendering"
    • No ambiguity about usage context
  4. Appropriate Complexity: Each extension matches its use case

    • Init templates don't need loops/conditionals (simple substitution is enough)
    • Runtime templates need full Jinja2 power (conditionals, loops, filters)
    • Using the right tool for the job

Codebase Statistics

Template Distribution:

  • .j2 templates: 134 files (88%) - Runtime generation
  • .template templates: 16 files (10%) - Workspace initialization
  • .tera templates: 3 files (2%) - Plugin examples

The two-tier system reflects the actual use case distribution in the codebase.

KCL Module Structure

Workspaces use a clear directory structure for KCL modules:

workspace/
├── config/
│   ├── kcl.mod              # Workspace package, imports provisioning from ../.kcl
│   └── provisioning.k       # Workspace-specific config overrides (SST pattern)
├── .provisioning/           # Metadata only
│   └── metadata.yaml        # Workspace metadata and version info
└── .kcl/                    # Main KCL package "provisioning"
    ├── kcl.mod              # Package definition
    ├── workspace_config.k   # Schema definitions (SST)
    ├── workspace_config_defaults.k  # Default values (SST)
    ├── batch.k
    ├── cluster.k
    └── ... other KCL modules

Directory Purposes

Directory Purpose Contents
.provisioning/ Metadata only metadata.yaml - workspace versioning and compatibility
.kcl/ KCL modules All KCL configuration files and schemas
config/ Workspace config Runtime configuration files generated from templates

SST Pattern (Single Source of Truth)

Workspace configuration follows the SST pattern:

  1. Schema (.kcl/workspace_config.k) - Type-safe schema definitions
  2. Defaults (.kcl/workspace_config_defaults.k) - Base default values
  3. Overrides (config/provisioning.k) - Workspace-specific customizations

Never edit .provisioning/workspace_config.k or .provisioning/workspace_config_defaults.k - they are copies only. Always edit in .kcl/ directory instead.

Important

These files are TEMPLATES ONLY. They are NEVER loaded at runtime.

The provisioning system generates workspace configurations from these templates during workspace initialization. Once generated, the workspace uses its own config/provisioning.yaml and related configs.

Available Templates

1. workspace-provisioning.yaml.template

Main workspace configuration template. Generates: {workspace}/config/provisioning.yaml

Variables:

  • {{workspace.name}} - Workspace name
  • {{workspace.path}} - Absolute workspace path
  • {{now.iso}} - Timestamp

Sections:

  • workspace - Workspace metadata
  • paths - All system paths
  • core - Core settings
  • debug - Debug configuration
  • output - Output preferences
  • providers - Provider settings
  • platform - Platform services
  • secrets - Secret management
  • kms - Key management
  • sops - SOPS configuration
  • taskservs - Task service paths
  • clusters - Cluster paths
  • cache - Cache settings

2. provider-aws.toml.template

AWS provider configuration template. Generates: {workspace}/config/providers/aws.toml

Variables:

  • {{workspace.name}} - Workspace name
  • {{workspace.path}} - Absolute workspace path
  • {{now.iso}} - Timestamp

Sections:

  • provider - Provider metadata
  • provider.auth - AWS authentication
  • provider.paths - Provider-specific paths
  • provider.api - API settings

3. provider-local.toml.template

Local provider configuration template. Generates: {workspace}/config/providers/local.toml

Variables:

  • {{workspace.name}} - Workspace name
  • {{workspace.path}} - Absolute workspace path
  • {{now.iso}} - Timestamp

Sections:

  • provider - Provider metadata
  • provider.auth - Local auth (minimal)
  • provider.paths - Provider-specific paths

4. provider-upcloud.toml.template

UpCloud provider configuration template. Generates: {workspace}/config/providers/upcloud.toml

Variables:

  • {{workspace.name}} - Workspace name
  • {{workspace.path}} - Absolute workspace path
  • {{now.iso}} - Timestamp

Sections:

  • provider - Provider metadata
  • provider.auth - UpCloud authentication
  • provider.paths - Provider-specific paths
  • provider.api - API settings (UpCloud API URL)

5. kms.toml.template

Key Management Service configuration template. Generates: {workspace}/config/kms.toml

Variables:

  • {{workspace.name}} - Workspace name
  • {{workspace.path}} - Absolute workspace path
  • {{now.iso}} - Timestamp

Sections:

  • kms - KMS mode and settings
  • kms.local - Local KMS (Age)
  • kms.remote - Remote KMS server

6. user-context.yaml.template

User context configuration template. Generates: ~/Library/Application Support/provisioning/ws_{name}.yaml

Variables:

  • {{workspace.name}} - Workspace name
  • {{workspace.path}} - Absolute workspace path
  • {{now.iso}} - Timestamp

Sections:

  • workspace - Workspace reference
  • debug - User debug overrides
  • output - User output preferences
  • providers - User provider preferences
  • paths - User path overrides

Template Variable Syntax

Templates use {{variable}} syntax for interpolation:

# Example
workspace:
  name: "{{workspace.name}}"
  path: "{{workspace.path}}"
  created: "{{now.iso}}"

Supported Variables

Core Variables

  • {{workspace.name}} - Workspace name (string)
  • {{workspace.path}} - Absolute workspace path (string)

Timestamp Variables

  • {{now.iso}} - ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SSZ)
  • {{now.date}} - Date only (YYYY-MM-DD)
  • {{now.timestamp}} - Unix timestamp

Environment Variables (safe list)

  • {{env.HOME}} - User home directory
  • {{env.USER}} - Current user
  • {{env.HOSTNAME}} - System hostname

Usage

Generate Workspace from Template

use provisioning/core/nulib/lib_provisioning/workspace/init.nu *

# Initialize workspace with AWS and Local providers
workspace-init "my-workspace" "/path/to/workspace" \
  --providers ["aws" "local"] \
  --activate

What Happens

  1. Templates are read from this directory
  2. Variables are interpolated with actual values
  3. Generated configs are saved to workspace
  4. User context (if --activate) is created

Generated Structure

/path/to/workspace/
├── config/
│   ├── provisioning.yaml      # From workspace-provisioning.yaml.template
│   ├── kms.toml               # From kms.toml.template
│   └── providers/
│       ├── aws.toml           # From provider-aws.toml.template
│       └── local.toml         # From provider-local.toml.template

~/Library/Application Support/provisioning/
└── ws_my-workspace.yaml        # From user-context.yaml.template

Adding New Templates

1. Create Template File

# Example: New provider template
touch provider-gcp.toml.template

2. Add Template Content

# GCP Provider Configuration for Workspace: {{workspace.name}}
# Generated: {{now.iso}}

[provider]
name = "gcp"
enabled = true
workspace = "{{workspace.name}}"

[provider.auth]
project = "default"
region = "us-central1"

[provider.paths]
base = "{{workspace.path}}/.providers/gcp"
cache = "{{workspace.path}}/.providers/gcp/cache"

3. Update Workspace Init

Add GCP template handling to workspace/init.nu:

def generate-provider-config [
  workspace_path: string
  workspace_name: string
  provider_name: string
] {
  let template_path = $"/path/to/templates/provider-($provider_name).toml.template"

  if not ($template_path | path exists) {
    print $"⚠️  No template for provider '($provider_name)'"
    return
  }

  # Generate config...
}

Template Best Practices

1. Always Include Metadata

# Configuration for Workspace: {{workspace.name}}
# Generated: {{now.iso}}
# DO NOT EDIT - Regenerate from template

2. Use Absolute Paths

paths:
  base: "{{workspace.path}}"           # ✅ Absolute
  cache: "{{workspace.path}}/.cache"   # ✅ Absolute

  # NOT relative:
  # cache: ".cache"                    # ❌ Relative

3. Provide Sensible Defaults

debug:
  enabled: false          # Safe default
  log_level: "info"       # Reasonable default

providers:
  default: "local"        # Safe default

4. Document Sections

# Debug settings (can be overridden by user context)
debug:
  enabled: false
  log_level: "info"
[kms]
mode = "local"

[kms.local]
provider = "age"
key_path = "{{workspace.path}}/.kms/keys/age.txt"

[kms.remote]
server = ""

Validation

Templates should be validated before use:

  1. Syntax Valid: YAML/TOML parseable
  2. Variables Complete: All {{variables}} have values
  3. Paths Absolute: All paths use {{workspace.path}}
  4. Sensible Defaults: Safe, secure defaults

Troubleshooting

Template Not Found

⚠️  Warning: No template found for provider 'xyz'

Solution: Create template or check provider name spelling.

Variable Not Interpolated

Config shows {{workspace.name}} instead of actual name.

Solution: Check variable exists in interpolation list, update workspace/init.nu.

Invalid YAML/TOML

Generated config fails to parse.

Solution: Validate template syntax, ensure proper escaping.

  • Workspace Init: provisioning/core/nulib/lib_provisioning/workspace/init.nu
  • Config Loader: provisioning/core/nulib/lib_provisioning/config/loader.nu
  • Documentation: docs/configuration/workspace-config-architecture.md

Summary

  • Templates are source files only, never loaded at runtime
  • Used to generate workspace configs during initialization
  • Support variable interpolation with {{variable}} syntax
  • Each template creates specific config file in workspace
  • Modify templates to change default workspace structure