nu_plugin_kcl/README.md
2025-09-20 15:48:32 +01:00

20 KiB

nu_plugin_kcl

A powerful Nushell plugin for KCL (Kubernetes Configuration Language) workflows, providing seamless integration between Nushell's data processing capabilities and KCL's configuration management system.

Overview

This plugin wraps the KCL CLI to provide native Nushell commands for running, formatting, and validating KCL configurations. Perfect for Infrastructure as Code (IaC), Kubernetes manifests, and configuration management workflows where you need to combine structured data processing with declarative configuration.

Installing

Caution

Require to have KCL CLI wrapper use KCL installation documentation

Clone this repository

Warning

nu_plugin_kcl has dependencies to nushell source via local path in Cargo.toml Nushell and plugins require to be sync with same version

Clone Nushell alongside this plugin or change dependencies in Cargo.toml

This plugin is also included as submodule in nushell-plugins as part of plugins collection for Provisioning project

Build from source

> cd nu_plugin_kcl
> cargo install --path .

Nushell

In a Nushell

> plugin add ~/.cargo/bin/nu_plugin_kcl

Commands

kcl-run

Execute KCL files and return their output with support for variables, output formats, and settings.

> kcl-run <file> [--format] [--output] [--define] [--setting]

Parameters:

  • file <path>: KCL file to execute

Flags:

  • --format -f <string>: Output format (yaml/json)
  • --output -o <path>: Output file path
  • --define -D <string>: Variables to define (key=value)
  • --setting -Y <string>: Setting files to include

Example:

> kcl-run myfile.k -D foo=bar -f json
{
  "foo": "bar"
}

kcl-format

Format KCL files according to standard KCL formatting rules.

> kcl-format <file>

Parameters:

  • file <path>: KCL file to format

Example:

> kcl-format myfile.k
✅ File formatted: myfile.k

kcl-validate

Validate all KCL files in a directory for syntax and semantic correctness.

> kcl-validate [directory]

Parameters:

  • directory <path>: Directory to validate (defaults to current directory)

Example:

> kcl-validate ./project_dir
✅ All 3 files are valid

✅ ./project_dir/main.k
✅ ./project_dir/vars.k
✅ ./project_dir/other.k

KCL Configuration Language

KCL is a constraint-based record and functional programming language hosted by CNCF. It provides powerful features for configuration management:

Basic Syntax

# Simple configuration
name = "my-app"
version = "1.0.0"

# Schema definition
schema Config:
    name: str
    version: str
    replicas: int = 3

# Configuration instance
config: Config = {
    name = name
    version = version
    replicas = 5
}

Advanced Features

# Constraints and validation
schema Service:
    name: str
    port: int

    check:
        1 <= port <= 65535, "port must be between 1 and 65535"
        len(name) > 0, "name cannot be empty"

# Conditional logic
config = {
    env = "production"
    database = {
        host = "prod-db.example.com" if env == "production" else "localhost"
        port = 5432
        ssl = True if env == "production" else False
    }
}

# List comprehensions and filters
services = [
    {name = "web-${i}", port = 8000 + i} for i in range(3)
] + [
    {name = "worker-${i}", port = 9000 + i} for i in range(2)
]

# Filtering
web_services = [s for s in services if "web" in s.name]

Usage Examples

Basic Configuration Management

app-config.k

# Application configuration schema
schema AppConfig:
    name: str
    version: str
    environment: str
    database: DatabaseConfig
    server: ServerConfig

schema DatabaseConfig:
    host: str
    port: int = 5432
    name: str
    ssl: bool = True

schema ServerConfig:
    host: str = "0.0.0.0"
    port: int = 8080
    workers: int = 4

# Configuration instance
config: AppConfig = {
    name = "my-microservice"
    version = "1.2.3"
    environment = "production"
    database = {
        host = "db.example.com"
        name = "myapp_prod"
        ssl = True
    }
    server = {
        port = 8080
        workers = 8
    }
}

Usage:

> kcl-run app-config.k -f yaml
name: my-microservice
version: 1.2.3
environment: production
database:
  host: db.example.com
  port: 5432
  name: myapp_prod
  ssl: true
server:
  host: 0.0.0.0
  port: 8080
  workers: 8

Dynamic Configuration with Variables

kubernetes-deployment.k

# Kubernetes deployment template
schema Deployment:
    apiVersion: str = "apps/v1"
    kind: str = "Deployment"
    metadata: {
        name: str
        namespace?: str
    }
    spec: {
        replicas: int
        selector: {
            matchLabels: {str:}
        }
        template: {
            metadata: {
                labels: {str:}
            }
            spec: {
                containers: [Container]
            }
        }
    }

schema Container:
    name: str
    image: str
    ports?: [{
        containerPort: int
        protocol?: str = "TCP"
    }]
    env?: [{
        name: str
        value: str
    }]

# Variables with defaults
app_name = option("app_name") or "my-app"
app_version = option("app_version") or "latest"
replicas = option("replicas") or 3
namespace = option("namespace") or "default"

# Generate deployment
deployment: Deployment = {
    metadata = {
        name = app_name
        namespace = namespace
    }
    spec = {
        replicas = replicas
        selector.matchLabels = {"app": app_name}
        template = {
            metadata.labels = {"app": app_name}
            spec.containers = [{
                name = app_name
                image = "${app_name}:${app_version}"
                ports = [{containerPort = 8080}]
                env = [
                    {name = "APP_NAME", value = app_name}
                    {name = "APP_VERSION", value = app_version}
                ]
            }]
        }
    }
}

Usage:

# Basic deployment
> kcl-run kubernetes-deployment.k -D app_name=web-service -D app_version=v1.2.3 -f yaml

# Multiple environment deployment
> ["dev", "staging", "prod"] | each { |env|
    kcl-run kubernetes-deployment.k -D app_name=web-service -D app_version=v1.2.3 -D namespace=$env -f yaml
    | save $"manifests/($env)/deployment.yaml"
}

Multi-Environment Configuration

base-config.k

# Base configuration shared across environments
schema BaseConfig:
    app: {
        name: str
        version: str
    }
    features: {
        auth: bool = True
        metrics: bool = True
        logging: bool = True
    }

base: BaseConfig = {
    app = {
        name = "my-service"
        version = "1.0.0"
    }
    features = {
        auth = True
        metrics = True
        logging = True
    }
}

environments/dev.k

import base

# Development environment overrides
config = base.base | {
    environment = "development"
    database = {
        host = "localhost"
        port = 5432
        name = "myapp_dev"
        ssl = False
    }
    server = {
        host = "localhost"
        port = 3000
        debug = True
    }
    features = base.base.features | {
        auth = False  # Disable auth in dev
    }
}

environments/prod.k

import base

# Production environment configuration
config = base.base | {
    environment = "production"
    database = {
        host = "prod-db.example.com"
        port = 5432
        name = "myapp_prod"
        ssl = True
        pool_size = 20
    }
    server = {
        host = "0.0.0.0"
        port = 8080
        workers = 16
        debug = False
    }
    security = {
        rate_limit = 1000
        cors_origins = ["https://myapp.com"]
    }
}

Usage:

# Generate configurations for all environments
> ["dev", "staging", "prod"] | each { |env|
    let config = (kcl-run $"environments/($env).k" -f json | from json)
    $config | to yaml | save $"configs/($env).yaml"
    print $"Generated config for ($env)"
}

Workflow Examples

Infrastructure as Code Pipeline

# Complete IaC workflow with KCL
def deploy-infrastructure [environment: string] {
    print $"🚀 Deploying infrastructure for ($environment)"

    # 1. Validate all KCL files
    print "📋 Validating KCL configurations..."
    let validation = (kcl-validate ./infrastructure)
    print $validation

    # 2. Generate environment-specific configs
    print $"⚙️  Generating ($environment) configuration..."
    let config = (kcl-run $"infrastructure/($environment).k" -f yaml)
    $config | save $"output/($environment)/config.yaml"

    # 3. Generate Kubernetes manifests
    print "🎯 Generating Kubernetes manifests..."
    ls infrastructure/kubernetes/*.k
    | each { |manifest|
        let name = ($manifest.name | path basename | str replace '.k' '')
        kcl-run $manifest.name -D environment=$environment -f yaml
        | save $"output/($environment)/k8s/($name).yaml"
    }

    # 4. Validate generated YAML
    print "✅ Validating generated manifests..."
    ls $"output/($environment)/k8s/*.yaml"
    | each { |file|
        # You could add kubectl validation here
        print $"Validated: ($file.name)"
    }

    print $"✨ Infrastructure deployment for ($environment) complete!"
}

# Usage
> deploy-infrastructure "staging"

Configuration Validation and Testing

# Comprehensive validation workflow
def validate-all-configs [] {
    print "🔍 Starting comprehensive validation..."

    # 1. Validate KCL syntax
    print "📝 Validating KCL syntax..."
    let kcl_validation = (kcl-validate .)
    print $kcl_validation

    # 2. Test all environment configurations
    print "🌍 Testing environment configurations..."
    let environments = ["dev", "staging", "prod"]
    let results = ($environments | each { |env|
        try {
            let config = (kcl-run $"environments/($env).k" -f json | from json)
            {
                environment: $env
                status: "✅ valid"
                config_keys: ($config | columns | length)
            }
        } catch { |err|
            {
                environment: $env
                status: "❌ invalid"
                error: $err.msg
            }
        }
    })

    $results | table

    # 3. Check for required configuration keys
    print "🔑 Checking required configuration keys..."
    let required_keys = ["app", "database", "server"]
    $environments | each { |env|
        let config = (kcl-run $"environments/($env).k" -f json | from json)
        let missing = ($required_keys | where $it not-in ($config | columns))
        if ($missing | length) > 0 {
            print $"⚠️  ($env): Missing keys: ($missing | str join ', ')"
        } else {
            print $"✅ ($env): All required keys present"
        }
    }
}

# Schema validation helper
def validate-schema [config_file: string, schema_file: string] {
    try {
        kcl-run $config_file -Y $schema_file -f json | from json
        print $"✅ ($config_file) validates against schema"
    } catch { |err|
        print $"❌ ($config_file) schema validation failed: ($err.msg)"
    }
}

Continuous Integration Integration

# CI/CD pipeline integration
def ci-kcl-pipeline [] {
    print "🏗️  Starting KCL CI pipeline..."

    # 1. Format check
    print "📐 Checking code formatting..."
    let unformatted = (ls **/*.k | each { |file|
        let original = (open $file.name)
        kcl-format $file.name
        let formatted = (open $file.name)
        if $original != $formatted {
            $file.name
        }
    } | compact)

    if ($unformatted | length) > 0 {
        print $"❌ Unformatted files found: ($unformatted | str join ', ')"
        exit 1
    }
    print "✅ All files properly formatted"

    # 2. Validation
    print "🔍 Validating configurations..."
    let validation = (kcl-validate .)
    if "❌" in $validation {
        print "❌ Validation failed"
        print $validation
        exit 1
    }
    print "✅ All configurations valid"

    # 3. Generate and test configurations
    print "⚙️  Testing configuration generation..."
    ["dev", "staging", "prod"] | each { |env|
        try {
            kcl-run $"environments/($env).k" -f json | from json | ignore
            print $"✅ ($env) configuration generates successfully"
        } catch { |err|
            print $"❌ ($env) configuration failed: ($err.msg)"
            exit 1
        }
    }

    print "🎉 KCL CI pipeline completed successfully!"
}

Configuration Management and Templating

# Generate application configurations from templates
def generate-app-config [app_name: string, version: string, environment: string] {
    let template = "templates/app-template.k"
    let settings = $"settings/($environment).yaml"

    kcl-run $template -D app_name=$app_name -D version=$version -Y $settings -f yaml
    | save $"configs/($app_name)-($environment).yaml"

    print $"Generated config for ($app_name) v($version) in ($environment)"
}

# Bulk configuration generation
def generate-all-configs [] {
    let apps = [
        {name: "web-service", version: "1.2.3"}
        {name: "api-service", version: "2.1.0"}
        {name: "worker-service", version: "1.5.2"}
    ]

    let environments = ["dev", "staging", "prod"]

    $apps | each { |app|
        $environments | each { |env|
            generate-app-config $app.name $app.version $env
        }
    }
}

# Configuration diff and comparison
def compare-configs [env1: string, env2: string] {
    print $"Comparing ($env1) vs ($env2) configurations..."

    let config1 = (kcl-run $"environments/($env1).k" -f json | from json)
    let config2 = (kcl-run $"environments/($env2).k" -f json | from json)

    # Find differences in configuration keys
    let keys1 = ($config1 | columns)
    let keys2 = ($config2 | columns)

    let only_in_env1 = ($keys1 | where $it not-in $keys2)
    let only_in_env2 = ($keys2 | where $it not-in $keys1)

    if ($only_in_env1 | length) > 0 {
        print $"Keys only in ($env1): ($only_in_env1 | str join ', ')"
    }

    if ($only_in_env2 | length) > 0 {
        print $"Keys only in ($env2): ($only_in_env2 | str join ', ')"
    }

    print "Configuration comparison complete"
}

Advanced KCL Patterns

# Modular configuration management
def build-modular-config [base_config: string, modules: list<string>, output: string] {
    # Combine base configuration with modules
    let module_imports = ($modules | each { |m| $"-Y ($m)" } | str join " ")

    kcl-run $base_config $module_imports -f yaml | save $output
    print $"Built modular configuration: ($output)"
}

# Configuration validation with custom rules
def validate-with-rules [config_file: string, rules_file: string] {
    try {
        kcl-run $config_file -Y $rules_file
        print $"✅ ($config_file) passes all validation rules"
        true
    } catch { |err|
        print $"❌ ($config_file) validation failed: ($err.msg)"
        false
    }
}

# Generate documentation from KCL schemas
def generate-config-docs [schema_dir: string] {
    ls $"($schema_dir)/*.k"
    | each { |schema|
        let schema_name = ($schema.name | path basename | str replace '.k' '')
        print $"## ($schema_name | str title-case)"
        print ""

        # Extract schema documentation (would need KCL introspection)
        # This is a simplified example
        open $schema.name | lines | each { |line|
            if ($line | str starts-with "# ") {
                $line | str replace "# " ""
            }
        } | str join "\n"

        print ""
    }
}

Integration with Nushell Data Processing

Leverage Nushell's powerful data manipulation with KCL configuration management:

# Process and generate configurations from CSV data
> open services.csv
  | each { |service|
      kcl-run service-template.k -D name=$service.name -D port=$service.port -f yaml
      | save $"configs/($service.name).yaml"
    }

# Combine multiple data sources for configuration
> let infrastructure = (sys)
> let settings = (open settings.json)
> {
    infrastructure: $infrastructure,
    settings: $settings,
    timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
  } | to json | save runtime-config.json
> kcl-run dynamic-config.k -Y runtime-config.json

# Batch process and validate configurations
> ls configs/*.k
  | par-each { |config|
      let result = (kcl-validate $config.name)
      {
        file: $config.name,
        valid: ("✅" in $result),
        result: $result
      }
    }
  | where valid == false

Error Handling

The plugin provides detailed error messages for common issues:

# Syntax errors in KCL files
> kcl-run broken-config.k
Error: Error executing KCL
  ╭─[calling kcl-run]
  │ KCL syntax error: unexpected token at line 5

# Missing files
> kcl-run nonexistent.k
Error: Error executing KCL
  ╭─[calling kcl-run]
  │ File not found: nonexistent.k

# Validation errors
> kcl-validate invalid-project/
❌ Validation failed in invalid-project/
  - main.k: schema validation error
  - config.k: undefined variable 'missing_var'

Features

  • KCL Execution - Run KCL files with variable substitution and output formatting
  • Code Formatting - Automatic KCL code formatting according to standards
  • Project Validation - Comprehensive validation of KCL projects and files
  • Variable Support - Dynamic configuration through command-line variables
  • Multiple Formats - Support for YAML and JSON output formats
  • Settings Integration - Include external setting files in KCL execution
  • Error Reporting - Detailed error messages with context
  • Nushell Integration - Seamless data flow between Nushell and KCL

Use Cases

  • Infrastructure as Code: Manage Kubernetes, Terraform, and cloud configurations
  • Configuration Management: Environment-specific application configurations
  • Policy as Code: Define and enforce organizational policies
  • CI/CD Pipelines: Validate and generate configurations in automated workflows
  • Multi-Environment Deployments: Consistent configurations across environments
  • Schema Validation: Ensure configuration correctness with KCL schemas
  • Template Generation: Dynamic configuration templates with variable substitution
  • Compliance Management: Configuration compliance checking and reporting

Best Practices

1. Organize KCL Projects

project/
├── schemas/              # Reusable schemas
│   ├── app.k
│   ├── database.k
│   └── server.k
├── environments/         # Environment-specific configs
│   ├── dev.k
│   ├── staging.k
│   └── prod.k
├── templates/           # Configuration templates
│   └── service.k
├── settings/           # External settings
│   └── common.yaml
└── output/            # Generated configurations
    ├── dev/
    ├── staging/
    └── prod/

2. Use Schema-Driven Development

# Define schemas first
schema AppConfig:
    name: str
    version: str
    replicas: int

    check:
        1 <= replicas <= 100, "replicas must be between 1 and 100"
        len(name) > 0, "name cannot be empty"

# Then implement configurations
config: AppConfig = {
    name = "my-app"
    version = "1.0.0"
    replicas = 3
}

3. Create Reusable Functions

# KCL helper functions
def kcl-dev [config: string] {
    kcl-run $config -D environment=dev -f yaml
}

def kcl-prod [config: string] {
    kcl-run $config -D environment=prod -Y settings/prod.yaml -f yaml
}

# Validation helpers
def validate-project [dir: string = "."] {
    kcl-validate $dir
}

def format-all-kcl [] {
    ls **/*.k | each { |file| kcl-format $file.name }
}

Contributing

Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.

License

This project is licensed under the MIT License.