KCL Module Organization Guide
This guide explains how to organize KCL modules and create extensions for the provisioning system.
Module Structure Overview
provisioning/
├── kcl/ # Core provisioning schemas
│ ├── settings.k # Main Settings schema
│ ├── defaults.k # Default configurations
│ └── main.k # Module entry point
├── extensions/
│ ├── kcl/ # KCL expects modules here
│ │ └── provisioning/0.0.1/ # Auto-generated from provisioning/kcl/
│ ├── providers/ # Cloud providers
│ │ ├── upcloud/kcl/
│ │ ├── aws/kcl/
│ │ └── local/kcl/
│ ├── taskservs/ # Infrastructure services
│ │ ├── kubernetes/kcl/
│ │ ├── cilium/kcl/
│ │ ├── redis/kcl/ # Our example
│ │ └── {service}/kcl/
│ └── clusters/ # Complete cluster definitions
└── config/ # TOML configuration files
workspace/
└── infra/
└── {your-infra}/ # Your infrastructure workspace
├── kcl.mod # Module dependencies
├── settings.k # Infrastructure settings
├── task-servs/ # Taskserver configurations
└── clusters/ # Cluster configurations
Import Path Conventions
1. Core Provisioning Schemas
# Import main provisioning schemas
import provisioning
# Use Settings schema
_settings = provisioning.Settings {
main_name = "my-infra"
# ... other settings
}
2. Taskserver Schemas
# Import specific taskserver
import taskservs.{service}.kcl.{service} as {service}_schema
# Examples:
import taskservs.kubernetes.kcl.kubernetes as k8s_schema
import taskservs.cilium.kcl.cilium as cilium_schema
import taskservs.redis.kcl.redis as redis_schema
# Use the schema
_taskserv = redis_schema.Redis {
version = "7.2.3"
port = 6379
}
3. Provider Schemas
# Import cloud provider schemas
import {provider}_prov.{provider} as {provider}_schema
# Examples:
import upcloud_prov.upcloud as upcloud_schema
import aws_prov.aws as aws_schema
4. Cluster Schemas
# Import cluster definitions
import cluster.{cluster_name} as {cluster}_schema
KCL Module Resolution Issues & Solutions
Problem: Path Resolution
KCL ignores the actual path in kcl.mod and uses convention-based resolution.
What you write in kcl.mod:
[dependencies]
provisioning = { path = "../../../provisioning/kcl", version = "0.0.1" }
Where KCL actually looks:
/provisioning/extensions/kcl/provisioning/0.0.1/
Solutions:
Solution 1: Use Expected Structure (Recommended)
Copy your KCL modules to where KCL expects them:
mkdir -p provisioning/extensions/kcl/provisioning/0.0.1
cp -r provisioning/kcl/* provisioning/extensions/kcl/provisioning/0.0.1/
Solution 2: Workspace-Local Copies
For development workspaces, copy modules locally:
cp -r ../../../provisioning/kcl workspace/infra/wuji/provisioning
Solution 3: Direct File Imports (Limited)
For simple cases, import files directly:
kcl run ../../../provisioning/kcl/settings.k
Creating New Taskservers
Directory Structure
provisioning/extensions/taskservs/{service}/
├── kcl/
│ ├── kcl.mod # Module definition
│ ├── {service}.k # KCL schema
│ └── dependencies.k # Optional dependencies
├── default/
│ ├── install-{service}.sh # Installation script
│ └── env-{service}.j2 # Environment template
└── README.md # Documentation
KCL Schema Template ({service}.k)
# Info: {Service} KCL schemas for provisioning
# Author: Your Name
# Release: 0.0.1
schema {Service}:
"""
{Service} configuration schema for infrastructure provisioning
"""
name: str = "{service}"
version: str
# Service-specific configuration
port: int = {default_port}
# Add your configuration options here
# Validation
check:
port > 0 and port < 65536, "Port must be between 1 and 65535"
len(version) > 0, "Version must be specified"
Module Configuration (kcl.mod)
[package]
name = "{service}"
edition = "v0.11.2"
version = "0.0.1"
[dependencies]
provisioning = { path = "../../../kcl", version = "0.0.1" }
taskservs = { path = "../..", version = "0.0.1" }
Usage in Workspace
# In workspace/infra/{your-infra}/task-servs/{service}.k
import taskservs.{service}.kcl.{service} as {service}_schema
_taskserv = {service}_schema.{Service} {
version = "1.0.0"
port = {port}
# ... your configuration
}
_taskserv
Workspace Setup
1. Create Workspace Directory
mkdir -p workspace/infra/{your-infra}/{task-servs,clusters,defs}
2. Create kcl.mod
[package]
name = "{your-infra}"
edition = "v0.11.2"
version = "0.0.1"
[dependencies]
provisioning = { path = "../../../provisioning/kcl", version = "0.0.1" }
taskservs = { path = "../../../provisioning/extensions/taskservs", version = "0.0.1" }
cluster = { path = "../../../provisioning/extensions/cluster", version = "0.0.1" }
upcloud_prov = { path = "../../../provisioning/extensions/providers/upcloud/kcl", version = "0.0.1" }
3. Create settings.k
import provisioning
_settings = provisioning.Settings {
main_name = "{your-infra}"
main_title = "{Your Infrastructure Title}"
# ... other settings
}
_settings
4. Test Configuration
cd workspace/infra/{your-infra}
kcl run settings.k
Common Patterns
Boolean Values
Use True and False (capitalized) in KCL:
enabled: bool = True
disabled: bool = False
Optional Fields
Use ? for optional fields:
optional_field?: str
Union Types
Use | for multiple allowed types:
log_level: "debug" | "info" | "warn" | "error" = "info"
Validation
Add validation rules:
check:
port > 0 and port < 65536, "Port must be valid"
len(name) > 0, "Name cannot be empty"
Testing Your Extensions
Test KCL Schema
cd workspace/infra/{your-infra}
kcl run task-servs/{service}.k
Test with Provisioning System
provisioning -c -i {your-infra} taskserv create {service}
Best Practices
- Use descriptive schema names:
Redis,Kubernetes, notredis,k8s - Add comprehensive validation: Check ports, required fields, etc.
- Provide sensible defaults: Make configuration easy to use
- Document all options: Use docstrings and comments
- Follow naming conventions: Use snake_case for fields, PascalCase for schemas
- Test thoroughly: Verify schemas work in workspaces
- Version properly: Use semantic versioning for modules
- Keep schemas focused: One service per schema file