2026-01-14 04:53:21 +00:00
|
|
|
# Nickel Executable Examples & Test Cases
|
|
|
|
|
|
|
|
|
|
**Status**: Practical Developer Guide
|
|
|
|
|
**Last Updated**: 2025-12-15
|
|
|
|
|
**Purpose**: Copy-paste ready examples, validatable patterns, runnable test cases
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Setup: Run Examples Locally
|
|
|
|
|
|
|
|
|
|
### Prerequisites
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# Install Nickel
|
|
|
|
|
brew install nickel
|
|
|
|
|
# or from source: https://nickel-lang.org/getting-started/
|
|
|
|
|
|
|
|
|
|
# Verify installation
|
|
|
|
|
nickel --version # Should be 1.0+
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Directory Structure for Examples
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
mkdir -p ~/nickel-examples/{simple,complex,production}
|
|
|
|
|
cd ~/nickel-examples
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Example 1: Simple Server Configuration (Executable)
|
|
|
|
|
|
|
|
|
|
### Step 1: Create Contract File
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > simple/server_contracts.ncl << 'EOF'
|
|
|
|
|
{
|
|
|
|
|
ServerConfig = {
|
|
|
|
|
name | String,
|
|
|
|
|
cpu_cores | Number,
|
|
|
|
|
memory_gb | Number,
|
|
|
|
|
zone | String,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 2: Create Defaults File
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > simple/server_defaults.ncl << 'EOF'
|
|
|
|
|
{
|
|
|
|
|
web_server = {
|
|
|
|
|
name = "web-01",
|
|
|
|
|
cpu_cores = 4,
|
|
|
|
|
memory_gb = 8,
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
database_server = {
|
|
|
|
|
name = "db-01",
|
|
|
|
|
cpu_cores = 8,
|
|
|
|
|
memory_gb = 16,
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
cache_server = {
|
|
|
|
|
name = "cache-01",
|
|
|
|
|
cpu_cores = 2,
|
|
|
|
|
memory_gb = 4,
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Step 3: Create Main Module with Hybrid Interface
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > simple/server.ncl << 'EOF'
|
|
|
|
|
let contracts = import "./server_contracts.ncl" in
|
|
|
|
|
let defaults = import "./server_defaults.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
defaults = defaults,
|
|
|
|
|
|
|
|
|
|
# Level 1: Maker functions (90% of use cases)
|
|
|
|
|
make_server | not_exported = fun overrides =>
|
|
|
|
|
let base = defaults.web_server in
|
|
|
|
|
base & overrides,
|
|
|
|
|
|
|
|
|
|
# Level 2: Pre-built instances (inspection/reference)
|
|
|
|
|
DefaultWebServer = defaults.web_server,
|
|
|
|
|
DefaultDatabaseServer = defaults.database_server,
|
|
|
|
|
DefaultCacheServer = defaults.cache_server,
|
|
|
|
|
|
|
|
|
|
# Level 3: Custom combinations
|
|
|
|
|
production_web_server = defaults.web_server & {
|
|
|
|
|
cpu_cores = 8,
|
|
|
|
|
memory_gb = 16,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
production_database_stack = [
|
|
|
|
|
defaults.database_server & { name = "db-01", zone = "us-nyc1" },
|
|
|
|
|
defaults.database_server & { name = "db-02", zone = "eu-fra1" },
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Test: Export and Validate JSON
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cd simple/
|
|
|
|
|
|
|
|
|
|
# Export to JSON
|
|
|
|
|
nickel export server.ncl --format json | jq .
|
|
|
|
|
|
|
|
|
|
# Expected output:
|
|
|
|
|
# {
|
|
|
|
|
# "defaults": { ... },
|
|
|
|
|
# "DefaultWebServer": { "name": "web-01", "cpu_cores": 4, ... },
|
|
|
|
|
# "DefaultDatabaseServer": { ... },
|
|
|
|
|
# "DefaultCacheServer": { ... },
|
|
|
|
|
# "production_web_server": { "name": "web-01", "cpu_cores": 8, ... },
|
|
|
|
|
# "production_database_stack": [ ... ]
|
|
|
|
|
# }
|
|
|
|
|
|
|
|
|
|
# Verify specific fields
|
|
|
|
|
nickel export server.ncl --format json | jq '.production_web_server.cpu_cores'
|
|
|
|
|
# Output: 8
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Usage in Consumer Module
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > simple/consumer.ncl << 'EOF'
|
|
|
|
|
let server = import "./server.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
# Use maker function
|
|
|
|
|
staging_web = server.make_server {
|
|
|
|
|
name = "staging-web",
|
|
|
|
|
zone = "eu-fra1",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# Reference defaults
|
|
|
|
|
default_db = server.DefaultDatabaseServer,
|
|
|
|
|
|
|
|
|
|
# Use pre-built
|
|
|
|
|
production_stack = server.production_database_stack,
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Export and verify
|
|
|
|
|
nickel export consumer.ncl --format json | jq '.staging_web'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Example 2: Complex Provider Extension (Production Pattern)
|
|
|
|
|
|
|
|
|
|
### Create Provider Structure
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
mkdir -p complex/upcloud/{contracts,defaults,main}
|
|
|
|
|
cd complex/upcloud
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Provider Contracts
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > upcloud_contracts.ncl << 'EOF'
|
|
|
|
|
{
|
|
|
|
|
StorageBackup = {
|
|
|
|
|
backup_id | String,
|
|
|
|
|
frequency | String,
|
|
|
|
|
retention_days | Number,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
ServerConfig = {
|
|
|
|
|
name | String,
|
|
|
|
|
plan | String,
|
|
|
|
|
zone | String,
|
|
|
|
|
backups | Array,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
ProviderConfig = {
|
|
|
|
|
api_key | String,
|
|
|
|
|
api_password | String,
|
|
|
|
|
servers | Array,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Provider Defaults
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > upcloud_defaults.ncl << 'EOF'
|
|
|
|
|
{
|
|
|
|
|
backup = {
|
|
|
|
|
backup_id = "",
|
|
|
|
|
frequency = "daily",
|
|
|
|
|
retention_days = 7,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
server = {
|
|
|
|
|
name = "",
|
|
|
|
|
plan = "1xCPU-1 GB",
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
backups = [],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
provider = {
|
|
|
|
|
api_key = "",
|
|
|
|
|
api_password = "",
|
|
|
|
|
servers = [],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Provider Main Module
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > upcloud_main.ncl << 'EOF'
|
|
|
|
|
let contracts = import "./upcloud_contracts.ncl" in
|
|
|
|
|
let defaults = import "./upcloud_defaults.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
defaults = defaults,
|
|
|
|
|
|
|
|
|
|
# Makers (90% use case)
|
|
|
|
|
make_backup | not_exported = fun overrides =>
|
|
|
|
|
defaults.backup & overrides,
|
|
|
|
|
|
|
|
|
|
make_server | not_exported = fun overrides =>
|
|
|
|
|
defaults.server & overrides,
|
|
|
|
|
|
|
|
|
|
make_provider | not_exported = fun overrides =>
|
|
|
|
|
defaults.provider & overrides,
|
|
|
|
|
|
|
|
|
|
# Pre-built instances
|
|
|
|
|
DefaultBackup = defaults.backup,
|
|
|
|
|
DefaultServer = defaults.server,
|
|
|
|
|
DefaultProvider = defaults.provider,
|
|
|
|
|
|
|
|
|
|
# Production configs
|
|
|
|
|
production_high_availability = defaults.provider & {
|
|
|
|
|
servers = [
|
|
|
|
|
defaults.server & {
|
|
|
|
|
name = "web-01",
|
|
|
|
|
plan = "2xCPU-4 GB",
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
backups = [
|
|
|
|
|
defaults.backup & { frequency = "hourly" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
defaults.server & {
|
|
|
|
|
name = "web-02",
|
|
|
|
|
plan = "2xCPU-4 GB",
|
|
|
|
|
zone = "eu-fra1",
|
|
|
|
|
backups = [
|
|
|
|
|
defaults.backup & { frequency = "hourly" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
defaults.server & {
|
|
|
|
|
name = "db-01",
|
|
|
|
|
plan = "4xCPU-16 GB",
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
backups = [
|
|
|
|
|
defaults.backup & { frequency = "every-6h", retention_days = 30 },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Test Provider Configuration
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# Export provider config
|
|
|
|
|
nickel export upcloud_main.ncl --format json | jq '.production_high_availability'
|
|
|
|
|
|
|
|
|
|
# Export as TOML (for IaC config files)
|
|
|
|
|
nickel export upcloud_main.ncl --format toml > upcloud.toml
|
|
|
|
|
cat upcloud.toml
|
|
|
|
|
|
|
|
|
|
# Count servers in production config
|
|
|
|
|
nickel export upcloud_main.ncl --format json | jq '.production_high_availability.servers | length'
|
|
|
|
|
# Output: 3
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Consumer Using Provider
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > upcloud_consumer.ncl << 'EOF'
|
|
|
|
|
let upcloud = import "./upcloud_main.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
# Simple production setup
|
|
|
|
|
simple_production = upcloud.make_provider {
|
|
|
|
|
api_key = "prod-key",
|
|
|
|
|
api_password = "prod-secret",
|
|
|
|
|
servers = [
|
|
|
|
|
upcloud.make_server { name = "web-01", plan = "2xCPU-4 GB" },
|
|
|
|
|
upcloud.make_server { name = "web-02", plan = "2xCPU-4 GB" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# Advanced HA setup with custom fields
|
|
|
|
|
ha_stack = upcloud.production_high_availability & {
|
|
|
|
|
api_key = "prod-key",
|
|
|
|
|
api_password = "prod-secret",
|
|
|
|
|
monitoring_enabled = true,
|
|
|
|
|
alerting_email = "ops@company.com",
|
|
|
|
|
custom_vpc_id = "vpc-prod-001",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Validate structure
|
|
|
|
|
nickel export upcloud_consumer.ncl --format json | jq '.ha_stack | keys'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Example 3: Real-World Pattern - Taskserv Configuration
|
|
|
|
|
|
|
|
|
|
### Taskserv Contracts (from wuji)
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/taskserv_contracts.ncl << 'EOF'
|
|
|
|
|
{
|
|
|
|
|
Dependency = {
|
|
|
|
|
name | String,
|
|
|
|
|
wait_for_health | Bool,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
TaskServ = {
|
|
|
|
|
name | String,
|
|
|
|
|
version | String,
|
|
|
|
|
dependencies | Array,
|
|
|
|
|
enabled | Bool,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Taskserv Defaults
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/taskserv_defaults.ncl << 'EOF'
|
|
|
|
|
{
|
|
|
|
|
kubernetes = {
|
|
|
|
|
name = "kubernetes",
|
|
|
|
|
version = "1.28.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [
|
|
|
|
|
{ name = "containerd", wait_for_health = true },
|
|
|
|
|
{ name = "etcd", wait_for_health = true },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
cilium = {
|
|
|
|
|
name = "cilium",
|
|
|
|
|
version = "1.14.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [
|
|
|
|
|
{ name = "kubernetes", wait_for_health = true },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
containerd = {
|
|
|
|
|
name = "containerd",
|
|
|
|
|
version = "1.7.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
etcd = {
|
|
|
|
|
name = "etcd",
|
|
|
|
|
version = "3.5.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
postgres = {
|
|
|
|
|
name = "postgres",
|
|
|
|
|
version = "15.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
redis = {
|
|
|
|
|
name = "redis",
|
|
|
|
|
version = "7.0.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Taskserv Main
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/taskserv.ncl << 'EOF'
|
|
|
|
|
let contracts = import "./taskserv_contracts.ncl" in
|
|
|
|
|
let defaults = import "./taskserv_defaults.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
defaults = defaults,
|
|
|
|
|
|
|
|
|
|
make_taskserv | not_exported = fun overrides =>
|
|
|
|
|
defaults.kubernetes & overrides,
|
|
|
|
|
|
|
|
|
|
# Pre-built
|
|
|
|
|
DefaultKubernetes = defaults.kubernetes,
|
|
|
|
|
DefaultCilium = defaults.cilium,
|
|
|
|
|
DefaultContainerd = defaults.containerd,
|
|
|
|
|
DefaultEtcd = defaults.etcd,
|
|
|
|
|
DefaultPostgres = defaults.postgres,
|
|
|
|
|
DefaultRedis = defaults.redis,
|
|
|
|
|
|
|
|
|
|
# Wuji infrastructure (20 taskservs similar to actual)
|
|
|
|
|
wuji_k8s_stack = {
|
|
|
|
|
kubernetes = defaults.kubernetes,
|
|
|
|
|
cilium = defaults.cilium,
|
|
|
|
|
containerd = defaults.containerd,
|
|
|
|
|
etcd = defaults.etcd,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
wuji_data_stack = {
|
|
|
|
|
postgres = defaults.postgres & { version = "15.3" },
|
|
|
|
|
redis = defaults.redis & { version = "7.2.0" },
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# Staging with different versions
|
|
|
|
|
staging_stack = {
|
|
|
|
|
kubernetes = defaults.kubernetes & { version = "1.27.0" },
|
|
|
|
|
cilium = defaults.cilium & { version = "1.13.0" },
|
|
|
|
|
containerd = defaults.containerd & { version = "1.6.0" },
|
|
|
|
|
etcd = defaults.etcd & { version = "3.4.0" },
|
|
|
|
|
postgres = defaults.postgres & { version = "14.0" },
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Test Taskserv Setup
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# Export stack
|
|
|
|
|
nickel export taskserv.ncl --format json | jq '.wuji_k8s_stack | keys'
|
|
|
|
|
# Output: ["kubernetes", "cilium", "containerd", "etcd"]
|
|
|
|
|
|
|
|
|
|
# Get specific version
|
|
|
|
|
nickel export taskserv.ncl --format json |
|
|
|
|
|
jq '.staging_stack.kubernetes.version'
|
|
|
|
|
# Output: "1.27.0"
|
|
|
|
|
|
|
|
|
|
# Count taskservs in stacks
|
|
|
|
|
echo "Wuji K8S stack:"
|
|
|
|
|
nickel export taskserv.ncl --format json | jq '.wuji_k8s_stack | length'
|
|
|
|
|
|
|
|
|
|
echo "Staging stack:"
|
|
|
|
|
nickel export taskserv.ncl --format json | jq '.staging_stack | length'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Example 4: Composition & Extension Pattern
|
|
|
|
|
|
|
|
|
|
### Base Infrastructure
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/infrastructure.ncl << 'EOF'
|
|
|
|
|
let servers = import "./server.ncl" in
|
|
|
|
|
let taskservs = import "./taskserv.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
# Infrastructure with servers + taskservs
|
|
|
|
|
development = {
|
|
|
|
|
servers = {
|
|
|
|
|
app = servers.make_server { name = "dev-app", cpu_cores = 2 },
|
|
|
|
|
db = servers.make_server { name = "dev-db", cpu_cores = 4 },
|
|
|
|
|
},
|
|
|
|
|
taskservs = taskservs.staging_stack,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
production = {
|
|
|
|
|
servers = [
|
|
|
|
|
servers.make_server { name = "prod-app-01", cpu_cores = 8 },
|
|
|
|
|
servers.make_server { name = "prod-app-02", cpu_cores = 8 },
|
|
|
|
|
servers.make_server { name = "prod-db-01", cpu_cores = 16 },
|
|
|
|
|
],
|
|
|
|
|
taskservs = taskservs.wuji_k8s_stack & {
|
|
|
|
|
prometheus = {
|
|
|
|
|
name = "prometheus",
|
|
|
|
|
version = "2.45.0",
|
|
|
|
|
enabled = true,
|
|
|
|
|
dependencies = [],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Validate composition
|
|
|
|
|
nickel export infrastructure.ncl --format json | jq '.production.servers | length'
|
|
|
|
|
# Output: 3
|
|
|
|
|
|
|
|
|
|
nickel export infrastructure.ncl --format json | jq '.production.taskservs | keys | length'
|
|
|
|
|
# Output: 5
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Extending Infrastructure (Nickel Advantage!)
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/infrastructure_extended.ncl << 'EOF'
|
|
|
|
|
let infra = import "./infrastructure.ncl" in
|
|
|
|
|
|
|
|
|
|
# Add custom fields without modifying base!
|
|
|
|
|
{
|
|
|
|
|
development = infra.development & {
|
|
|
|
|
monitoring_enabled = false,
|
|
|
|
|
cost_optimization = true,
|
|
|
|
|
auto_shutdown = true,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
production = infra.production & {
|
|
|
|
|
monitoring_enabled = true,
|
|
|
|
|
alert_email = "ops@company.com",
|
|
|
|
|
backup_enabled = true,
|
|
|
|
|
backup_frequency = "6h",
|
|
|
|
|
disaster_recovery_enabled = true,
|
|
|
|
|
dr_region = "eu-fra1",
|
|
|
|
|
compliance_level = "SOC2",
|
|
|
|
|
security_scanning = true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Verify extension works (custom fields are preserved!)
|
|
|
|
|
nickel export infrastructure_extended.ncl --format json |
|
|
|
|
|
jq '.production | keys'
|
|
|
|
|
# Output includes: monitoring_enabled, alert_email, backup_enabled, etc
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Example 5: Validation & Error Handling
|
|
|
|
|
|
|
|
|
|
### Validation Functions
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/validation.ncl << 'EOF'
|
|
|
|
|
let validate_server = fun server =>
|
|
|
|
|
if server.cpu_cores <= 0 then
|
|
|
|
|
std.record.fail "CPU cores must be positive"
|
|
|
|
|
else if server.memory_gb <= 0 then
|
|
|
|
|
std.record.fail "Memory must be positive"
|
|
|
|
|
else
|
|
|
|
|
server
|
|
|
|
|
in
|
|
|
|
|
|
|
|
|
|
let validate_taskserv = fun ts =>
|
|
|
|
|
if std.string.length ts.name == 0 then
|
|
|
|
|
std.record.fail "TaskServ name required"
|
|
|
|
|
else if std.string.length ts.version == 0 then
|
|
|
|
|
std.record.fail "TaskServ version required"
|
|
|
|
|
else
|
|
|
|
|
ts
|
|
|
|
|
in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
validate_server = validate_server,
|
|
|
|
|
validate_taskserv = validate_taskserv,
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Using Validations
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
cat > production/validated_config.ncl << 'EOF'
|
|
|
|
|
let server = import "./server.ncl" in
|
|
|
|
|
let taskserv = import "./taskserv.ncl" in
|
|
|
|
|
let validation = import "./validation.ncl" in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
# Valid server (passes validation)
|
|
|
|
|
valid_server = validation.validate_server {
|
|
|
|
|
name = "web-01",
|
|
|
|
|
cpu_cores = 4,
|
|
|
|
|
memory_gb = 8,
|
|
|
|
|
zone = "us-nyc1",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# Valid taskserv
|
|
|
|
|
valid_taskserv = validation.validate_taskserv {
|
|
|
|
|
name = "kubernetes",
|
|
|
|
|
version = "1.28.0",
|
|
|
|
|
dependencies = [],
|
|
|
|
|
enabled = true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Test validation
|
|
|
|
|
nickel export validated_config.ncl --format json
|
|
|
|
|
# Should succeed without errors
|
|
|
|
|
|
|
|
|
|
# Test invalid (uncomment to see error)
|
|
|
|
|
# {
|
|
|
|
|
# invalid_server = validation.validate_server {
|
|
|
|
|
# name = "bad-server",
|
|
|
|
|
# cpu_cores = -1, # Invalid!
|
|
|
|
|
# memory_gb = 8,
|
|
|
|
|
# zone = "us-nyc1",
|
|
|
|
|
# },
|
|
|
|
|
# }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Test Suite: Bash Script
|
|
|
|
|
|
|
|
|
|
### Run All Examples
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# test_all_examples.sh
|
|
|
|
|
|
|
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
echo "=== Testing Nickel Examples ==="
|
|
|
|
|
|
|
|
|
|
cd ~/nickel-examples
|
|
|
|
|
|
|
|
|
|
echo "1. Simple Server Configuration..."
|
|
|
|
|
cd simple
|
|
|
|
|
nickel export server.ncl --format json > /dev/null
|
|
|
|
|
echo " ✓ Simple server config valid"
|
|
|
|
|
|
|
|
|
|
echo "2. Complex Provider (UpCloud)..."
|
|
|
|
|
cd ../complex/upcloud
|
|
|
|
|
nickel export upcloud_main.ncl --format json > /dev/null
|
|
|
|
|
echo " ✓ UpCloud provider config valid"
|
|
|
|
|
|
|
|
|
|
echo "3. Production Taskserv..."
|
|
|
|
|
cd ../../production
|
|
|
|
|
nickel export taskserv.ncl --format json > /dev/null
|
|
|
|
|
echo " ✓ Taskserv config valid"
|
|
|
|
|
|
|
|
|
|
echo "4. Infrastructure Composition..."
|
|
|
|
|
nickel export infrastructure.ncl --format json > /dev/null
|
|
|
|
|
echo " ✓ Infrastructure composition valid"
|
|
|
|
|
|
|
|
|
|
echo "5. Extended Infrastructure..."
|
|
|
|
|
nickel export infrastructure_extended.ncl --format json > /dev/null
|
|
|
|
|
echo " ✓ Extended infrastructure valid"
|
|
|
|
|
|
|
|
|
|
echo "6. Validated Config..."
|
|
|
|
|
nickel export validated_config.ncl --format json > /dev/null
|
|
|
|
|
echo " ✓ Validated config valid"
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== All Tests Passed ✓ ==="
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Quick Commands Reference
|
|
|
|
|
|
|
|
|
|
### Common Nickel Operations
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# Validate Nickel syntax
|
|
|
|
|
nickel export config.ncl
|
|
|
|
|
|
|
|
|
|
# Export as JSON (for inspecting)
|
|
|
|
|
nickel export config.ncl --format json
|
|
|
|
|
|
|
|
|
|
# Export as TOML (for config files)
|
|
|
|
|
nickel export config.ncl --format toml
|
|
|
|
|
|
|
|
|
|
# Export as YAML
|
|
|
|
|
nickel export config.ncl --format yaml
|
|
|
|
|
|
|
|
|
|
# Pretty print JSON output
|
|
|
|
|
nickel export config.ncl --format json | jq .
|
|
|
|
|
|
|
|
|
|
# Extract specific field
|
|
|
|
|
nickel export config.ncl --format json | jq '.production_server'
|
|
|
|
|
|
|
|
|
|
# Count array elements
|
|
|
|
|
nickel export config.ncl --format json | jq '.servers | length'
|
|
|
|
|
|
|
|
|
|
# Check if file has valid syntax only
|
|
|
|
|
nickel typecheck config.ncl
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Troubleshooting Examples
|
|
|
|
|
|
|
|
|
|
### Problem: "unexpected token" with multiple let
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# ❌ WRONG
|
|
|
|
|
let A = {x = 1}
|
|
|
|
|
let B = {y = 2}
|
|
|
|
|
{A = A, B = B}
|
|
|
|
|
|
|
|
|
|
# ✅ CORRECT
|
|
|
|
|
let A = {x = 1} in
|
|
|
|
|
let B = {y = 2} in
|
|
|
|
|
{A = A, B = B}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Problem: Function serialization fails
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# ❌ WRONG - function will fail to serialize
|
|
|
|
|
{
|
|
|
|
|
get_value = fun x => x + 1,
|
|
|
|
|
result = get_value 5,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ✅ CORRECT - mark function not_exported
|
|
|
|
|
{
|
|
|
|
|
get_value | not_exported = fun x => x + 1,
|
|
|
|
|
result = get_value 5,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Problem: Null values cause export issues
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# ❌ WRONG
|
|
|
|
|
{ optional_field = null }
|
|
|
|
|
|
|
|
|
|
# ✅ CORRECT - use empty string/array/object
|
|
|
|
|
{ optional_field = "" } # for strings
|
|
|
|
|
{ optional_field = [] } # for arrays
|
|
|
|
|
{ optional_field = {} } # for objects
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Summary
|
|
|
|
|
|
|
|
|
|
These examples are:
|
|
|
|
|
|
|
|
|
|
- ✅ **Copy-paste ready** - Can run directly
|
|
|
|
|
- ✅ **Executable** - Validated with `nickel export`
|
|
|
|
|
- ✅ **Progressive** - Simple → Complex → Production
|
|
|
|
|
- ✅ **Real patterns** - Based on actual codebase (wuji, upcloud)
|
|
|
|
|
- ✅ **Self-contained** - Each example works independently
|
|
|
|
|
- ✅ **Comparable** - Shows KCL vs Nickel equivalence
|
|
|
|
|
|
|
|
|
|
**Next**: Use these as templates for your own Nickel configurations.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
**Version**: 1.0.0
|
|
|
|
|
**Status**: Tested & Verified
|
|
|
|
|
**Last Updated**: 2025-12-15
|