981 lines
34 KiB
Plaintext
981 lines
34 KiB
Plaintext
|
|
#!/usr/bin/env nu
|
||
|
|
|
||
|
|
# Enhanced Module Loader CLI
|
||
|
|
# Unified CLI for discovering and loading taskservs, providers, and clusters
|
||
|
|
# Includes template and layer support from enhanced version
|
||
|
|
|
||
|
|
use ../nulib/taskservs/discover.nu *
|
||
|
|
use ../nulib/taskservs/load.nu *
|
||
|
|
use ../nulib/providers/discover.nu *
|
||
|
|
use ../nulib/providers/load.nu *
|
||
|
|
use ../nulib/clusters/discover.nu *
|
||
|
|
use ../nulib/clusters/load.nu *
|
||
|
|
use ../nulib/lib_provisioning/kcl_module_loader.nu *
|
||
|
|
use ../nulib/lib_provisioning/config/accessor.nu config-get
|
||
|
|
|
||
|
|
# Main module loader command with enhanced features
|
||
|
|
def main [subcommand?: string] {
|
||
|
|
if ($subcommand | is-empty) {
|
||
|
|
print_enhanced_help
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
match $subcommand {
|
||
|
|
"help" => print_enhanced_help
|
||
|
|
"discover" => print_discover_help
|
||
|
|
"load" => print_load_help
|
||
|
|
"list" => print_list_help
|
||
|
|
"unload" => print_unload_help
|
||
|
|
"init" => print_init_help
|
||
|
|
"validate" => print_validate_help
|
||
|
|
"info" => print_info_help
|
||
|
|
"template" => print_template_help
|
||
|
|
"layer" => print_layer_help
|
||
|
|
"override" => print_override_help
|
||
|
|
_ => {
|
||
|
|
print $"Unknown command: ($subcommand)"
|
||
|
|
print_enhanced_help
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === DISCOVERY COMMANDS ===
|
||
|
|
|
||
|
|
# Discover available modules
|
||
|
|
export def "main discover" [
|
||
|
|
type: string, # Module type: taskservs, providers, clusters
|
||
|
|
query?: string, # Search query
|
||
|
|
--format: string = "table", # Output format: table, yaml, json, names
|
||
|
|
--category: string = "", # Filter by category (for taskservs)
|
||
|
|
--group: string = "" # Filter by group (for taskservs)
|
||
|
|
] {
|
||
|
|
match $type {
|
||
|
|
"taskservs" => {
|
||
|
|
let taskservs = if ($query | is-empty) {
|
||
|
|
discover-taskservs
|
||
|
|
} else {
|
||
|
|
search-taskservs $query
|
||
|
|
}
|
||
|
|
|
||
|
|
let filtered = if ($category | is-empty) and ($group | is-empty) {
|
||
|
|
$taskservs
|
||
|
|
} else if not ($category | is-empty) {
|
||
|
|
$taskservs | where group == $category
|
||
|
|
} else if not ($group | is-empty) {
|
||
|
|
$taskservs | where group == $group
|
||
|
|
} else {
|
||
|
|
$taskservs
|
||
|
|
}
|
||
|
|
|
||
|
|
format_output $filtered $format
|
||
|
|
}
|
||
|
|
"providers" => {
|
||
|
|
print "Provider discovery not implemented yet"
|
||
|
|
}
|
||
|
|
"clusters" => {
|
||
|
|
print "Cluster discovery not implemented yet"
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
print $"Unknown module type: ($type)"
|
||
|
|
print "Available types: taskservs, providers, clusters"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Sync KCL dependencies for infrastructure workspace
|
||
|
|
export def "main sync-kcl" [
|
||
|
|
infra: string, # Infrastructure name or path
|
||
|
|
--manifest: string = "providers.manifest.yaml", # Manifest file name
|
||
|
|
--kcl # Show KCL module info after sync
|
||
|
|
] {
|
||
|
|
# Resolve infrastructure path
|
||
|
|
let infra_path = if ($infra | path exists) {
|
||
|
|
$infra
|
||
|
|
} else {
|
||
|
|
# Try workspace path
|
||
|
|
let workspace_path = $"workspace/infra/($infra)"
|
||
|
|
if ($workspace_path | path exists) {
|
||
|
|
$workspace_path
|
||
|
|
} else {
|
||
|
|
print $"❌ Infrastructure not found: ($infra)"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Sync KCL dependencies using library function
|
||
|
|
sync-kcl-dependencies $infra_path --manifest $manifest
|
||
|
|
|
||
|
|
# Show KCL module info if requested
|
||
|
|
if $kcl {
|
||
|
|
print ""
|
||
|
|
print "📋 KCL Modules:"
|
||
|
|
let modules_dir = (get-config-value "kcl" "modules_dir")
|
||
|
|
let modules_path = ($infra_path | path join $modules_dir)
|
||
|
|
|
||
|
|
if ($modules_path | path exists) {
|
||
|
|
ls $modules_path | each {|entry|
|
||
|
|
print $" • ($entry.name | path basename) → ($entry.name)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === LOAD/UNLOAD COMMANDS ===
|
||
|
|
|
||
|
|
# Load modules into workspace
|
||
|
|
export def "main load" [
|
||
|
|
type: string, # Module type: taskservs, providers, clusters
|
||
|
|
workspace: string, # Workspace path
|
||
|
|
...modules: string, # Module names to load
|
||
|
|
--layer: string = "workspace", # Layer to load into: workspace, infra
|
||
|
|
--validate # Validate after loading
|
||
|
|
--force (-f) # Force overwrite existing files
|
||
|
|
] {
|
||
|
|
if ($modules | is-empty) {
|
||
|
|
print $"No modules specified for loading"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"Loading ($modules | length) ($type) into ($workspace) at layer ($layer)"
|
||
|
|
|
||
|
|
match $type {
|
||
|
|
"taskservs" | "providers" | "clusters" | "workflows" => {
|
||
|
|
load_extension_to_workspace $type $workspace $modules $layer $force
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
print $"Unknown module type: ($type)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if $validate {
|
||
|
|
main validate $workspace
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Enhanced load with template support
|
||
|
|
export def "main load enhanced" [
|
||
|
|
type: string, # Module type
|
||
|
|
workspace: string, # Workspace path
|
||
|
|
infra: string, # Infrastructure name
|
||
|
|
modules: list<string>, # Module names
|
||
|
|
--layer: string = "workspace", # Target layer
|
||
|
|
--template-base # Use template as base
|
||
|
|
] {
|
||
|
|
print $"🚀 Enhanced loading ($modules | length) ($type) for infra ($infra)"
|
||
|
|
|
||
|
|
for module in $modules {
|
||
|
|
print $" 📦 Loading ($module)..."
|
||
|
|
|
||
|
|
# Check if template exists for this module
|
||
|
|
let template_path = $"provisioning/workspace/templates/taskservs/*/($module).k"
|
||
|
|
let has_template = (glob $template_path | length) > 0
|
||
|
|
|
||
|
|
if $has_template and $template_base {
|
||
|
|
print $" ✓ Using template base for ($module)"
|
||
|
|
# Template-based loading would go here
|
||
|
|
} else {
|
||
|
|
print $" ✓ Direct loading for ($module)"
|
||
|
|
# Direct loading
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print "✅ Enhanced loading completed"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Unload module from workspace
|
||
|
|
export def "main unload" [
|
||
|
|
type: string, # Module type
|
||
|
|
workspace: string, # Workspace path
|
||
|
|
module: string, # Module name to unload
|
||
|
|
--layer: string = "workspace" # Layer to unload from
|
||
|
|
] {
|
||
|
|
print $"Unloading ($module) from ($workspace) at layer ($layer)"
|
||
|
|
|
||
|
|
match $type {
|
||
|
|
"taskservs" => {
|
||
|
|
unload_taskserv_from_workspace $workspace $module $layer
|
||
|
|
}
|
||
|
|
"providers" => {
|
||
|
|
print "Provider unloading not implemented yet"
|
||
|
|
}
|
||
|
|
"clusters" => {
|
||
|
|
print "Cluster unloading not implemented yet"
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
print $"Unknown module type: ($type)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === LIST COMMANDS ===
|
||
|
|
|
||
|
|
# List modules in workspace
|
||
|
|
export def "main list" [
|
||
|
|
type: string, # Module type
|
||
|
|
workspace: string, # Workspace path
|
||
|
|
--layer: string = "all", # Layer to list: workspace, infra, all
|
||
|
|
--format: string = "table" # Output format
|
||
|
|
] {
|
||
|
|
print $"Listing ($type) in ($workspace) for layer ($layer)"
|
||
|
|
|
||
|
|
match $type {
|
||
|
|
"taskservs" => {
|
||
|
|
list_workspace_taskservs $workspace $layer $format
|
||
|
|
}
|
||
|
|
"providers" => {
|
||
|
|
print "Provider listing not implemented yet"
|
||
|
|
}
|
||
|
|
"clusters" => {
|
||
|
|
print "Cluster listing not implemented yet"
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
print $"Unknown module type: ($type)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === TEMPLATE COMMANDS ===
|
||
|
|
|
||
|
|
# List available templates
|
||
|
|
export def "main template list" [
|
||
|
|
--template-type: string = "all", # Template type: taskservs, providers, servers, clusters
|
||
|
|
--format: string = "table" # Output format
|
||
|
|
] {
|
||
|
|
print $"📋 Available templates type: ($template_type)"
|
||
|
|
|
||
|
|
let template_base = "provisioning/workspace/templates"
|
||
|
|
|
||
|
|
match $template_type {
|
||
|
|
"taskservs" | "all" => {
|
||
|
|
let taskserv_templates = if (($template_base | path join "taskservs") | path exists) {
|
||
|
|
glob ($template_base | path join "taskservs" "*" "*.k")
|
||
|
|
| each { |path|
|
||
|
|
let category = ($path | path dirname | path basename)
|
||
|
|
let name = ($path | path basename | str replace ".k" "")
|
||
|
|
{ type: "taskserv", category: $category, name: $name, path: $path }
|
||
|
|
}
|
||
|
|
} else { [] }
|
||
|
|
|
||
|
|
format_output $taskserv_templates $format
|
||
|
|
}
|
||
|
|
"providers" => {
|
||
|
|
print "Provider templates not implemented yet"
|
||
|
|
}
|
||
|
|
"servers" => {
|
||
|
|
let server_templates = if (($template_base | path join "servers") | path exists) {
|
||
|
|
ls ($template_base | path join "servers") | get name
|
||
|
|
| each { |path| { type: "server", name: ($path | path basename), path: $path } }
|
||
|
|
} else { [] }
|
||
|
|
|
||
|
|
format_output $server_templates $format
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
print $"Unknown template type: ($template_type)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Extract template from existing infrastructure
|
||
|
|
export def "main template extract" [
|
||
|
|
source_infra: string, # Source infrastructure path
|
||
|
|
template_name: string, # Name for the new template
|
||
|
|
--type: string = "taskserv", # Template type
|
||
|
|
--output: string = "provisioning/workspace/templates" # Output directory
|
||
|
|
] {
|
||
|
|
print $"📤 Extracting template ($template_name) from ($source_infra)"
|
||
|
|
|
||
|
|
# Implementation would analyze the source infra and create template
|
||
|
|
print "Template extraction not yet implemented"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Apply template to infrastructure
|
||
|
|
export def "main template apply" [
|
||
|
|
template_name: string, # Template to apply
|
||
|
|
target_infra: string, # Target infrastructure
|
||
|
|
--override-file: string = "", # Override file path
|
||
|
|
--dry-run # Show what would be done
|
||
|
|
] {
|
||
|
|
if $dry_run {
|
||
|
|
print $"🔍 [DRY RUN] Would apply template ($template_name) to ($target_infra)"
|
||
|
|
} else {
|
||
|
|
print $"📥 Applying template ($template_name) to ($target_infra)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Implementation would apply template with overrides
|
||
|
|
print "Template application not yet implemented"
|
||
|
|
}
|
||
|
|
|
||
|
|
# === LAYER COMMANDS ===
|
||
|
|
|
||
|
|
# Show layer information
|
||
|
|
export def "main layer show" [
|
||
|
|
workspace: string, # Workspace path
|
||
|
|
--module: string = "", # Specific module to show
|
||
|
|
--type: string = "taskservs" # Module type
|
||
|
|
] {
|
||
|
|
print $"📊 Layer information for ($workspace)"
|
||
|
|
|
||
|
|
if not ($module | is-empty) {
|
||
|
|
# Use existing layer utilities
|
||
|
|
try {
|
||
|
|
nu -c $"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($module) ($workspace) upcloud"
|
||
|
|
} catch {
|
||
|
|
print $"Could not test layer resolution for ($module)"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print "Showing overall layer structure..."
|
||
|
|
try {
|
||
|
|
nu -c "use provisioning/workspace/tools/layer-utils.nu *; show_layer_stats"
|
||
|
|
} catch {
|
||
|
|
print "Could not show layer statistics"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Test layer resolution
|
||
|
|
export def "main layer test" [
|
||
|
|
module: string, # Module to test
|
||
|
|
workspace: string, # Workspace/infra name
|
||
|
|
provider: string = "upcloud" # Provider for testing
|
||
|
|
] {
|
||
|
|
print $"🧪 Testing layer resolution: ($module) in ($workspace) with ($provider)"
|
||
|
|
|
||
|
|
try {
|
||
|
|
nu -c $"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($module) ($workspace) ($provider)"
|
||
|
|
} catch {
|
||
|
|
print $"❌ Layer resolution test failed for ($module)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === OVERRIDE COMMANDS ===
|
||
|
|
|
||
|
|
# Create configuration override
|
||
|
|
export def "main override create" [
|
||
|
|
type: string, # Type: taskservs, providers, clusters
|
||
|
|
infra: string, # Infrastructure name
|
||
|
|
module: string, # Module name
|
||
|
|
--from: string = "", # Template to base override on
|
||
|
|
--layer: string = "infra" # Layer for override
|
||
|
|
] {
|
||
|
|
print $"⚙️ Creating override for ($module) in ($infra) at layer ($layer)"
|
||
|
|
|
||
|
|
let override_path = match $layer {
|
||
|
|
"infra" => $"workspace/infra/($infra)/overrides/($module).k"
|
||
|
|
"workspace" => $"provisioning/workspace/templates/($type)/($module).k"
|
||
|
|
_ => {
|
||
|
|
print $"Unknown layer: ($layer)"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"📝 Override will be created at: ($override_path)"
|
||
|
|
|
||
|
|
if not ($from | is-empty) {
|
||
|
|
print $"📋 Based on template: ($from)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create directory if needed
|
||
|
|
mkdir ($override_path | path dirname)
|
||
|
|
|
||
|
|
# Create basic override file
|
||
|
|
let content = if not ($from | is-empty) {
|
||
|
|
$"# Override for ($module) in ($infra)
|
||
|
|
# Based on template: ($from)
|
||
|
|
|
||
|
|
import ($type).*.($module).kcl.($module) as base
|
||
|
|
import provisioning.workspace.templates.($type).($from) as template
|
||
|
|
|
||
|
|
# Infrastructure-specific overrides
|
||
|
|
($module)_($infra)_override: base.($module | str capitalize) = template.($from)_template {
|
||
|
|
# Add your overrides here
|
||
|
|
# Example:
|
||
|
|
# replicas = 3
|
||
|
|
# resources.memory = \"1Gi\"
|
||
|
|
}
|
||
|
|
"
|
||
|
|
} else {
|
||
|
|
$"# Override for ($module) in ($infra)
|
||
|
|
|
||
|
|
import ($type).*.($module).kcl.($module) as base
|
||
|
|
|
||
|
|
# Infrastructure-specific overrides
|
||
|
|
($module)_($infra)_override: base.($module | str capitalize) = base.($module)_config {
|
||
|
|
# Add your overrides here
|
||
|
|
# Example:
|
||
|
|
# replicas = 3
|
||
|
|
# resources.memory = \"1Gi\"
|
||
|
|
}
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
$content | save $override_path
|
||
|
|
print $"✅ Override created: ($override_path)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# === WORKSPACE MANAGEMENT ===
|
||
|
|
|
||
|
|
# Initialize workspace with modules
|
||
|
|
export def "main init" [
|
||
|
|
workspace: string, # Workspace path
|
||
|
|
--modules: list<string> = [], # Initial modules to load
|
||
|
|
--template: string = "", # Workspace template
|
||
|
|
--provider: string = "upcloud" # Default provider
|
||
|
|
] {
|
||
|
|
print $"🚀 Initializing workspace: ($workspace)"
|
||
|
|
|
||
|
|
# Create workspace structure
|
||
|
|
let workspace_dirs = [
|
||
|
|
$"($workspace)/config"
|
||
|
|
$"($workspace)/taskservs"
|
||
|
|
$"($workspace)/overrides"
|
||
|
|
$"($workspace)/defs"
|
||
|
|
$"($workspace)/clusters"
|
||
|
|
]
|
||
|
|
|
||
|
|
for dir in $workspace_dirs {
|
||
|
|
mkdir $dir
|
||
|
|
print $" 📁 Created: ($dir)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create basic configuration
|
||
|
|
let config_content = $"# Workspace configuration for ($workspace)
|
||
|
|
# Provider: ($provider)
|
||
|
|
# Initialized: (date now)
|
||
|
|
|
||
|
|
provider = "($provider)"
|
||
|
|
workspace = "($workspace)"
|
||
|
|
"
|
||
|
|
$config_content | save $"($workspace)/config/workspace.toml"
|
||
|
|
print $" 📄 Created: ($workspace)/config/workspace.toml"
|
||
|
|
|
||
|
|
# Load initial modules
|
||
|
|
if ($modules | length) > 0 {
|
||
|
|
print $"📦 Loading initial modules: (($modules | str join ', '))"
|
||
|
|
main load taskservs $workspace ...$modules
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"✅ Workspace ($workspace) initialized successfully"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate workspace integrity
|
||
|
|
export def "main validate" [workspace: string] {
|
||
|
|
print $"🔍 Validating workspace: ($workspace)"
|
||
|
|
|
||
|
|
let required_dirs = ["config", "taskservs", "overrides", "defs"]
|
||
|
|
mut validation_errors = []
|
||
|
|
|
||
|
|
for dir in $required_dirs {
|
||
|
|
let full_path = ($workspace | path join $dir)
|
||
|
|
if not ($full_path | path exists) {
|
||
|
|
$validation_errors = ($validation_errors | append $"Missing directory: ($full_path)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check configuration file
|
||
|
|
let config_file = ($workspace | path join "config" "workspace.toml")
|
||
|
|
if not ($config_file | path exists) {
|
||
|
|
$validation_errors = ($validation_errors | append $"Missing configuration: ($config_file)")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Report results
|
||
|
|
if ($validation_errors | is-empty) {
|
||
|
|
print "✅ Workspace validation passed"
|
||
|
|
return true
|
||
|
|
} else {
|
||
|
|
print "❌ Workspace validation failed:"
|
||
|
|
for error in $validation_errors {
|
||
|
|
print $" • ($error)"
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show workspace information
|
||
|
|
export def "main info" [workspace: string] {
|
||
|
|
print $"📊 Workspace Information: ($workspace)"
|
||
|
|
|
||
|
|
if not (($workspace | path join "config" "workspace.toml") | path exists) {
|
||
|
|
print "❌ Workspace not found or not initialized"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show basic info
|
||
|
|
let config = try { open ($workspace | path join "config" "workspace.toml") | from toml } catch { {} }
|
||
|
|
|
||
|
|
print $" Provider: (($config.provider? | default 'unknown'))"
|
||
|
|
print $" Path: ($workspace)"
|
||
|
|
|
||
|
|
# Count modules
|
||
|
|
let taskserv_count = try {
|
||
|
|
ls ($workspace | path join "taskservs") | length
|
||
|
|
} catch { 0 }
|
||
|
|
|
||
|
|
let override_count = try {
|
||
|
|
ls ($workspace | path join "overrides") | length
|
||
|
|
} catch { 0 }
|
||
|
|
|
||
|
|
print $" Task Services: ($taskserv_count)"
|
||
|
|
print $" Overrides: ($override_count)"
|
||
|
|
|
||
|
|
# Show recent activity
|
||
|
|
let recent_files = try {
|
||
|
|
ls $workspace | where type == file | sort-by modified | last 3 | get name
|
||
|
|
} catch { [] }
|
||
|
|
|
||
|
|
if ($recent_files | length) > 0 {
|
||
|
|
print " Recent activity:"
|
||
|
|
for file in $recent_files {
|
||
|
|
print $" • ($file | path basename)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === HELPER FUNCTIONS ===
|
||
|
|
|
||
|
|
# Generic extension loading function (taskservs, providers, clusters, workflows)
|
||
|
|
def load_extension_to_workspace [
|
||
|
|
extension_type: string, # taskservs, providers, clusters, workflows
|
||
|
|
workspace: string,
|
||
|
|
modules: list<string>,
|
||
|
|
layer: string,
|
||
|
|
force: bool = false
|
||
|
|
] {
|
||
|
|
# Get extension-specific info function based on type
|
||
|
|
let get_info_fn = match $extension_type {
|
||
|
|
"taskservs" => { |name| get-taskserv-info $name }
|
||
|
|
"providers" => { |name| get-provider-info $name }
|
||
|
|
"clusters" => { |name| get-cluster-info $name }
|
||
|
|
_ => { |name| {name: $name, group: "", type: $extension_type} }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get source path from config
|
||
|
|
let source_base_path = (config-get $"paths.($extension_type)" | path expand)
|
||
|
|
|
||
|
|
# Get template base path from config
|
||
|
|
let provisioning_base = (config-get "paths.base" | path expand)
|
||
|
|
let template_base_path = ($provisioning_base | path join "workspace" "templates" $extension_type)
|
||
|
|
|
||
|
|
for module in $modules {
|
||
|
|
print $" 📦 Loading ($extension_type): ($module)"
|
||
|
|
|
||
|
|
# Get module info
|
||
|
|
let module_info = try {
|
||
|
|
do $get_info_fn $module
|
||
|
|
} catch {
|
||
|
|
print $" ❌ Module not found: ($module)"
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
print $" ✓ Found: ($module_info.name) (($module_info.group? | default ""))"
|
||
|
|
|
||
|
|
# Resolve workspace paths
|
||
|
|
let workspace_abs = ($workspace | path expand)
|
||
|
|
let workspace_root = if ($workspace_abs | str contains "/infra/") {
|
||
|
|
let parts = ($workspace_abs | split row "/infra/")
|
||
|
|
$parts.0
|
||
|
|
} else {
|
||
|
|
$workspace_abs
|
||
|
|
}
|
||
|
|
|
||
|
|
# Build source path (handle optional group, "root" means no category)
|
||
|
|
let group_path = ($module_info.group? | default "")
|
||
|
|
let group_path = if ($group_path == "root") { "" } else { $group_path }
|
||
|
|
let source_module_path = if ($group_path | is-not-empty) {
|
||
|
|
$source_base_path | path join $group_path $module
|
||
|
|
} else {
|
||
|
|
$source_base_path | path join $module
|
||
|
|
}
|
||
|
|
|
||
|
|
# STEP 1: Copy schemas to workspace/.{extension_type}
|
||
|
|
let target_schemas_dir = ($workspace_root | path join $".($extension_type)")
|
||
|
|
let target_module_path = if ($group_path | is-not-empty) {
|
||
|
|
$target_schemas_dir | path join $group_path $module
|
||
|
|
} else {
|
||
|
|
$target_schemas_dir | path join $module
|
||
|
|
}
|
||
|
|
|
||
|
|
# Config file directory
|
||
|
|
let config_dir = ($workspace_abs | path join $extension_type)
|
||
|
|
let config_file_path = ($config_dir | path join $"($module).k")
|
||
|
|
|
||
|
|
# Check if already loaded
|
||
|
|
if ($config_file_path | path exists) and ($target_module_path | path exists) {
|
||
|
|
if not $force {
|
||
|
|
print $" ✅ Module already loaded: ($module)"
|
||
|
|
print $" Config: ($config_file_path)"
|
||
|
|
print $" Source: ($target_module_path)"
|
||
|
|
print $" 💡 Use --force to overwrite existing files"
|
||
|
|
continue
|
||
|
|
} else {
|
||
|
|
print $" 🔄 Overwriting existing module: ($module)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Copy schemas from system extensions to workspace
|
||
|
|
let parent_dir = ($target_module_path | path dirname)
|
||
|
|
mkdir $parent_dir
|
||
|
|
|
||
|
|
if ($source_module_path | path exists) {
|
||
|
|
print $" 📦 Copying schemas to workspace .($extension_type)..."
|
||
|
|
print $" From: ($source_module_path)"
|
||
|
|
print $" To: ($target_module_path)"
|
||
|
|
|
||
|
|
if ($target_module_path | path exists) {
|
||
|
|
rm -rf $target_module_path
|
||
|
|
}
|
||
|
|
|
||
|
|
cp -r $source_module_path $parent_dir
|
||
|
|
print $" ✓ Schemas copied to workspace .($extension_type)/"
|
||
|
|
|
||
|
|
# STEP 2a: Update individual module's kcl.mod with correct workspace paths
|
||
|
|
# Calculate relative paths based on categorization depth
|
||
|
|
let provisioning_path = if ($group_path | is-not-empty) {
|
||
|
|
# Categorized: .{ext}/{category}/{module}/kcl/ -> ../../../../.kcl/packages/provisioning
|
||
|
|
"../../../../.kcl/packages/provisioning"
|
||
|
|
} else {
|
||
|
|
# Non-categorized: .{ext}/{module}/kcl/ -> ../../../.kcl/packages/provisioning
|
||
|
|
"../../../.kcl/packages/provisioning"
|
||
|
|
}
|
||
|
|
|
||
|
|
let parent_path = if ($group_path | is-not-empty) {
|
||
|
|
# Categorized: .{ext}/{category}/{module}/kcl/ -> ../../..
|
||
|
|
"../../.."
|
||
|
|
} else {
|
||
|
|
# Non-categorized: .{ext}/{module}/kcl/ -> ../..
|
||
|
|
"../.."
|
||
|
|
}
|
||
|
|
|
||
|
|
# Update the module's kcl.mod file with workspace-relative paths
|
||
|
|
let module_kcl_mod_path = ($target_module_path | path join "kcl" "kcl.mod")
|
||
|
|
if ($module_kcl_mod_path | path exists) {
|
||
|
|
print $" 🔧 Updating module kcl.mod with workspace paths"
|
||
|
|
let module_kcl_mod_content = $"[package]
|
||
|
|
name = \"($module)\"
|
||
|
|
edition = \"v0.11.3\"
|
||
|
|
version = \"0.0.1\"
|
||
|
|
|
||
|
|
[dependencies]
|
||
|
|
provisioning = { path = \"($provisioning_path)\", version = \"0.0.1\" }
|
||
|
|
($extension_type) = { path = \"($parent_path)\", version = \"0.1.0\" }
|
||
|
|
"
|
||
|
|
$module_kcl_mod_content | save -f $module_kcl_mod_path
|
||
|
|
print $" ✓ Updated kcl.mod: ($module_kcl_mod_path)"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print $" ⚠️ Warning: Source not found at ($source_module_path)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# STEP 2b: Create kcl.mod in workspace/.{extension_type}
|
||
|
|
let extension_kcl_mod = ($target_schemas_dir | path join "kcl.mod")
|
||
|
|
if not ($extension_kcl_mod | path exists) {
|
||
|
|
print $" 📦 Creating kcl.mod for .($extension_type) package"
|
||
|
|
let kcl_mod_content = $"[package]
|
||
|
|
name = \"($extension_type)\"
|
||
|
|
edition = \"v0.11.3\"
|
||
|
|
version = \"0.1.0\"
|
||
|
|
description = \"Workspace-level ($extension_type) schemas\"
|
||
|
|
"
|
||
|
|
$kcl_mod_content | save $extension_kcl_mod
|
||
|
|
}
|
||
|
|
|
||
|
|
# Ensure config directory exists
|
||
|
|
mkdir $config_dir
|
||
|
|
|
||
|
|
# STEP 4: Generate config from template
|
||
|
|
let template_path = if ($group_path | is-not-empty) {
|
||
|
|
$template_base_path | path join $group_path $"($module).k"
|
||
|
|
} else {
|
||
|
|
$template_base_path | path join $"($module).k"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Build import statement with "as {module}" alias
|
||
|
|
let import_stmt = if ($group_path | is-not-empty) {
|
||
|
|
$"import ($extension_type).($group_path).($module).kcl.($module) as ($module)"
|
||
|
|
} else {
|
||
|
|
$"import ($extension_type).($module).kcl.($module) as ($module)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get relative paths for comments
|
||
|
|
let workspace_name = ($workspace_root | path basename)
|
||
|
|
let relative_schema_path = if ($group_path | is-not-empty) {
|
||
|
|
$"($workspace_name)/.($extension_type)/($group_path)/($module)"
|
||
|
|
} else {
|
||
|
|
$"($workspace_name)/.($extension_type)/($module)"
|
||
|
|
}
|
||
|
|
|
||
|
|
let config_content = if ($template_path | path exists) {
|
||
|
|
print $" 📄 Using template from: ($template_path)"
|
||
|
|
let template_body = (open $template_path)
|
||
|
|
$"# Configuration for ($module)
|
||
|
|
# Workspace: ($workspace_name)
|
||
|
|
# Schemas from: ($relative_schema_path)
|
||
|
|
($import_stmt)
|
||
|
|
|
||
|
|
($template_body)"
|
||
|
|
} else {
|
||
|
|
$"# Configuration for ($module)
|
||
|
|
# Workspace: ($workspace_name)
|
||
|
|
# Schemas from: ($relative_schema_path)
|
||
|
|
($import_stmt)
|
||
|
|
|
||
|
|
# TODO: Configure your ($module) instance
|
||
|
|
# See available schemas at: ($relative_schema_path)/kcl/
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
$config_content | save -f $config_file_path
|
||
|
|
print $" ✓ Config created: ($config_file_path)"
|
||
|
|
print $" 📝 Edit ($extension_type)/($module).k to configure settings"
|
||
|
|
|
||
|
|
# STEP 3: Update infra kcl.mod
|
||
|
|
if ($workspace_abs | str contains "/infra/") {
|
||
|
|
let kcl_mod_path = ($workspace_abs | path join "kcl.mod")
|
||
|
|
if ($kcl_mod_path | path exists) {
|
||
|
|
let kcl_mod_content = (open $kcl_mod_path)
|
||
|
|
if not ($kcl_mod_content | str contains $"($extension_type) =") {
|
||
|
|
print $" 🔧 Updating kcl.mod to include ($extension_type) dependency"
|
||
|
|
let new_dependency = $"\n# Workspace-level ($extension_type) \(shared across infras\)\n($extension_type) = { path = \"../../.($extension_type)\" }\n"
|
||
|
|
$"($kcl_mod_content)($new_dependency)" | save -f $kcl_mod_path
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Unload taskserv from workspace
|
||
|
|
def unload_taskserv_from_workspace [workspace: string, module: string, layer: string] {
|
||
|
|
let target_path = match $layer {
|
||
|
|
"workspace" => ($workspace | path join "taskservs" $"($module).k")
|
||
|
|
"infra" => ($workspace | path join "overrides" $"($module).k")
|
||
|
|
_ => ($workspace | path join "taskservs" $"($module).k")
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($target_path | path exists) {
|
||
|
|
rm $target_path
|
||
|
|
print $" ✓ Removed: ($target_path)"
|
||
|
|
} else {
|
||
|
|
print $" ❌ Not found: ($target_path)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# List workspace taskservs
|
||
|
|
def list_workspace_taskservs [workspace: string, layer: string, format: string] {
|
||
|
|
let paths = match $layer {
|
||
|
|
"workspace" => [($workspace | path join "taskservs")]
|
||
|
|
"infra" => [($workspace | path join "overrides")]
|
||
|
|
"all" => [($workspace | path join "taskservs"), ($workspace | path join "overrides")]
|
||
|
|
_ => [($workspace | path join "taskservs")]
|
||
|
|
}
|
||
|
|
|
||
|
|
mut all_taskservs = []
|
||
|
|
|
||
|
|
for path in $paths {
|
||
|
|
if ($path | path exists) {
|
||
|
|
let taskservs = ls $path
|
||
|
|
| where type == file
|
||
|
|
| where name =~ '\\.k$'
|
||
|
|
| each { |file|
|
||
|
|
{
|
||
|
|
name: ($file.name | path basename | str replace ".k" "")
|
||
|
|
layer: ($path | path basename)
|
||
|
|
path: $file.name
|
||
|
|
modified: $file.modified
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$all_taskservs = ($all_taskservs | append $taskservs)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
format_output $all_taskservs $format
|
||
|
|
}
|
||
|
|
|
||
|
|
# Format output based on requested format
|
||
|
|
def format_output [data: any, format: string] {
|
||
|
|
match $format {
|
||
|
|
"json" => ($data | to json)
|
||
|
|
"yaml" => ($data | to yaml)
|
||
|
|
"names" => ($data | get name | str join "\n")
|
||
|
|
"table" | _ => ($data | table)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# === HELP FUNCTIONS ===
|
||
|
|
|
||
|
|
def print_enhanced_help [] {
|
||
|
|
print "Enhanced Module Loader CLI - Discovery and loading with template support"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader <command> [options]"
|
||
|
|
print ""
|
||
|
|
print "CORE COMMANDS:"
|
||
|
|
print " discover <type> [query] [--format <fmt>] [--category <cat>] - Discover available modules"
|
||
|
|
print " sync-kcl <infra> [--manifest <file>] [--kcl] - Sync KCL dependencies for infrastructure"
|
||
|
|
print " load <type> <workspace> <modules...> [--layer <layer>] - Load modules into workspace"
|
||
|
|
print " list <type> <workspace> [--layer <layer>] - List loaded modules"
|
||
|
|
print " unload <type> <workspace> <module> [--layer <layer>] - Unload module from workspace"
|
||
|
|
print ""
|
||
|
|
print "WORKSPACE COMMANDS:"
|
||
|
|
print " init <workspace> [--modules <list>] [--template <name>] - Initialize workspace"
|
||
|
|
print " validate <workspace> - Validate workspace integrity"
|
||
|
|
print " info <workspace> - Show workspace information"
|
||
|
|
print ""
|
||
|
|
print "TEMPLATE COMMANDS:"
|
||
|
|
print " template list [--type <type>] [--format <fmt>] - List available templates"
|
||
|
|
print " template extract <source> <name> [--type <type>] - Extract template from infra"
|
||
|
|
print " template apply <template> <target> [--dry-run] - Apply template to infra"
|
||
|
|
print ""
|
||
|
|
print "LAYER COMMANDS:"
|
||
|
|
print " layer show <workspace> [--module <name>] - Show layer information"
|
||
|
|
print " layer test <module> <workspace> [provider] - Test layer resolution"
|
||
|
|
print ""
|
||
|
|
print "OVERRIDE COMMANDS:"
|
||
|
|
print " override create <type> <infra> <module> [--from <template>] - Create configuration override"
|
||
|
|
print ""
|
||
|
|
print "ENHANCED COMMANDS:"
|
||
|
|
print " load enhanced <type> <workspace> <infra> <modules> [--layer <layer>] - Enhanced template loading"
|
||
|
|
print ""
|
||
|
|
print "Types: taskservs, providers, clusters"
|
||
|
|
print "Layers: workspace, infra, all"
|
||
|
|
print "Formats: table, json, yaml, names"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader discover taskservs --category databases"
|
||
|
|
print " module-loader load taskservs ./workspace [redis, postgres]"
|
||
|
|
print " module-loader template list --type taskservs"
|
||
|
|
print " module-loader layer test redis wuji upcloud"
|
||
|
|
print " module-loader override create taskservs wuji kubernetes --from ha-cluster"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_discover_help [] {
|
||
|
|
print "Discover available modules"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader discover <type> [query] [options]"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --format <fmt> Output format: table, json, yaml, names (default: table)"
|
||
|
|
print " --category <cat> Filter by category (taskservs only)"
|
||
|
|
print " --group <group> Filter by group (taskservs only)"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader discover taskservs"
|
||
|
|
print " module-loader discover taskservs redis"
|
||
|
|
print " module-loader discover taskservs --category databases"
|
||
|
|
print " module-loader discover taskservs --format json"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_load_help [] {
|
||
|
|
print "Load modules into workspace"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader load <type> <workspace> <modules...> [options]"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --layer <layer> Target layer: workspace, infra (default: workspace)"
|
||
|
|
print " --validate Validate workspace after loading"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader load taskservs ./workspace [kubernetes, cilium]"
|
||
|
|
print " module-loader load taskservs ./workspace [redis] --layer infra"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_list_help [] {
|
||
|
|
print "List modules in workspace"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader list <type> <workspace> [options]"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --layer <layer> Layer to list: workspace, infra, all (default: all)"
|
||
|
|
print " --format <fmt> Output format: table, json, yaml, names"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader list taskservs ./workspace"
|
||
|
|
print " module-loader list taskservs ./workspace --layer workspace"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_unload_help [] {
|
||
|
|
print "Unload module from workspace"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader unload <type> <workspace> <module> [options]"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --layer <layer> Layer to unload from: workspace, infra (default: workspace)"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader unload taskservs ./workspace kubernetes"
|
||
|
|
print " module-loader unload taskservs ./workspace redis --layer infra"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_init_help [] {
|
||
|
|
print "Initialize workspace with modules"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader init <workspace> [options]"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --modules <list> Initial modules to load"
|
||
|
|
print " --template <name> Workspace template to use"
|
||
|
|
print " --provider <name> Default provider (default: upcloud)"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader init ./my-workspace"
|
||
|
|
print " module-loader init ./k8s-workspace --modules [kubernetes, cilium]"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_validate_help [] {
|
||
|
|
print "Validate workspace integrity"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader validate <workspace>"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader validate ./workspace"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_info_help [] {
|
||
|
|
print "Show workspace information"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader info <workspace>"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader info ./workspace"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_template_help [] {
|
||
|
|
print "Template management commands"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader template <subcommand> [options]"
|
||
|
|
print ""
|
||
|
|
print "Subcommands:"
|
||
|
|
print " list List available templates"
|
||
|
|
print " extract Extract template from existing infrastructure"
|
||
|
|
print " apply Apply template to infrastructure"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader template list --type taskservs"
|
||
|
|
print " module-loader template extract ./wuji wuji-production"
|
||
|
|
print " module-loader template apply wuji-production ./new-infra"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_layer_help [] {
|
||
|
|
print "Layer resolution commands"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader layer <subcommand> [options]"
|
||
|
|
print ""
|
||
|
|
print "Subcommands:"
|
||
|
|
print " show Show layer information for workspace"
|
||
|
|
print " test Test layer resolution for specific module"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader layer show ./workspace"
|
||
|
|
print " module-loader layer test kubernetes wuji upcloud"
|
||
|
|
}
|
||
|
|
|
||
|
|
def print_override_help [] {
|
||
|
|
print "Configuration override commands"
|
||
|
|
print ""
|
||
|
|
print "Usage: module-loader override create <type> <infra> <module> [options]"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --from <template> Base override on template"
|
||
|
|
print " --layer <layer> Target layer: infra, workspace (default: infra)"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " module-loader override create taskservs wuji kubernetes"
|
||
|
|
print " module-loader override create taskservs wuji redis --from databases/redis"
|
||
|
|
}
|