Update core components including CLI, Nushell libraries, plugins system, and utility scripts for the provisioning system. CLI Updates: - Command implementations - CLI utilities and dispatching - Help system improvements - Command validation Library Updates: - Configuration management system - Infrastructure validation - Extension system improvements - Secrets management - Workspace operations - Cache management system Plugin System: - Interactive form plugin (inquire) - KCL integration plugin - Performance optimization plugins - Plugin registration system Utilities: - Build and distribution scripts - Installation procedures - Testing utilities - Development tools Documentation: - Library module documentation - Extension API guides - Plugin usage guides - Service management documentation All changes are backward compatible. No breaking changes.
1112 lines
37 KiB
Plaintext
1112 lines
37 KiB
Plaintext
# Utility Command Handlers
|
|
# Handles: ssh, sed, sops, cache, providers, nu, list, qr
|
|
|
|
use ../flags.nu *
|
|
use ../../lib_provisioning *
|
|
use ../../servers/ssh.nu *
|
|
use ../../servers/utils.nu *
|
|
|
|
# Helper to run module commands
|
|
def run_module [
|
|
args: string
|
|
module: string
|
|
option?: string
|
|
--exec
|
|
] {
|
|
let use_debug = if ($env.PROVISIONING_DEBUG? | default false) { "-x" } else { "" }
|
|
|
|
if $exec {
|
|
exec $"($env.PROVISIONING_NAME)" $use_debug -mod $module ($option | default "") $args
|
|
} else {
|
|
^$"($env.PROVISIONING_NAME)" $use_debug -mod $module ($option | default "") $args
|
|
}
|
|
}
|
|
|
|
# Main utility command dispatcher
|
|
export def handle_utility_command [
|
|
command: string
|
|
ops: string
|
|
flags: record
|
|
] {
|
|
match $command {
|
|
"ssh" => { handle_ssh $flags }
|
|
"sed" | "sops" => { handle_sops_edit $command $ops $flags }
|
|
"cache" => { handle_cache $ops $flags }
|
|
"providers" => { handle_providers $ops $flags }
|
|
"nu" => { handle_nu $ops $flags }
|
|
"list" | "l" | "ls" => { handle_list $ops $flags }
|
|
"qr" => { handle_qr }
|
|
"nuinfo" => { handle_nuinfo }
|
|
"plugin" | "plugins" => { handle_plugins $ops $flags }
|
|
"guide" | "guides" | "howto" => { handle_guide $ops $flags }
|
|
_ => {
|
|
print $"❌ Unknown utility command: ($command)"
|
|
print ""
|
|
print "Available utility commands:"
|
|
print " ssh - SSH into server"
|
|
print " sed - Edit SOPS encrypted files (alias)"
|
|
print " sops - Edit SOPS encrypted files"
|
|
print " cache - Cache management (status, config, clear, list)"
|
|
print " providers - List available providers"
|
|
print " nu - Start Nushell with provisioning library loaded"
|
|
print " list - List resources (servers, taskservs, clusters)"
|
|
print " qr - Generate QR code"
|
|
print " nuinfo - Show Nushell version info"
|
|
print " plugin - Plugin management (list, register, test, status)"
|
|
print " guide - Show interactive guides (from-scratch, update, customize)"
|
|
print ""
|
|
print "Use 'provisioning help utilities' for more details"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# SSH command handler
|
|
def handle_ssh [flags: record] {
|
|
let curr_settings = (find_get_settings --infra $flags.infra --settings $flags.settings $flags.include_notuse)
|
|
rm -rf $curr_settings.wk_path
|
|
server_ssh $curr_settings "" "pub" false
|
|
}
|
|
|
|
# SOPS edit command handler
|
|
def handle_sops_edit [task: string, ops: string, flags: record] {
|
|
let pos = if $task == "sed" { 0 } else { 1 }
|
|
let ops_parts = ($ops | split row " ")
|
|
let target_file = if ($ops_parts | length) > $pos { $ops_parts | get $pos } else { "" }
|
|
|
|
if ($target_file | is-empty) {
|
|
throw-error $"🛑 No file found" $"for (_ansi yellow_bold)sops(_ansi reset) edit"
|
|
exit -1
|
|
}
|
|
|
|
let target_full_path = if not ($target_file | path exists) {
|
|
let infra_path = (get_infra $flags.infra)
|
|
let candidate = ($infra_path | path join $target_file)
|
|
if ($candidate | path exists) {
|
|
$candidate
|
|
} else {
|
|
throw-error $"🛑 No file (_ansi green_italic)($target_file)(_ansi reset) found" $"for (_ansi yellow_bold)sops(_ansi reset) edit"
|
|
exit -1
|
|
}
|
|
} else {
|
|
$target_file
|
|
}
|
|
|
|
# Setup SOPS environment if needed
|
|
if ($env.PROVISIONING_SOPS? | is-empty) {
|
|
let curr_settings = (find_get_settings --infra $flags.infra --settings $flags.settings $flags.include_notuse)
|
|
rm -rf $curr_settings.wk_path
|
|
$env.CURRENT_INFRA_PATH = ($curr_settings.infra_path | path join $curr_settings.infra)
|
|
use ../../sops_env.nu
|
|
}
|
|
|
|
if $task == "sed" {
|
|
on_sops "sed" $target_full_path
|
|
} else {
|
|
on_sops $task $target_full_path ($ops_parts | skip 1)
|
|
}
|
|
}
|
|
|
|
# Cache command handler
|
|
def handle_cache [ops: string, flags: record] {
|
|
use ../../lib_provisioning/config/cache/simple-cache.nu *
|
|
|
|
# Parse cache subcommand
|
|
let parts = if ($ops | is-not-empty) {
|
|
($ops | str trim | split row " " | where { |x| ($x | is-not-empty) })
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
let subcommand = if ($parts | length) > 0 { $parts | get 0 } else { "status" }
|
|
let args = if ($parts | length) > 1 { $parts | skip 1 } else { [] }
|
|
|
|
# Handle cache commands
|
|
match $subcommand {
|
|
"status" => {
|
|
print ""
|
|
cache-status
|
|
print ""
|
|
}
|
|
|
|
"config" => {
|
|
let config_cmd = if ($args | length) > 0 { $args | get 0 } else { "show" }
|
|
match $config_cmd {
|
|
"show" => {
|
|
print ""
|
|
let config = (get-cache-config)
|
|
let cache_base = (($env.HOME? | default "~" | path expand) | path join ".provisioning" "cache" "config")
|
|
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
print "📋 Cache Configuration"
|
|
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
print ""
|
|
|
|
print "▸ Core Settings:"
|
|
let enabled = ($config | get --optional enabled | default true)
|
|
print (" Enabled: " + ($enabled | into string))
|
|
print ""
|
|
|
|
print "▸ Cache Location:"
|
|
print (" Base Path: " + $cache_base)
|
|
print ""
|
|
|
|
print "▸ Time-To-Live (TTL) Settings:"
|
|
let ttl_final = ($config | get --optional ttl_final_config | default "300")
|
|
let ttl_kcl = ($config | get --optional ttl_kcl | default "1800")
|
|
let ttl_sops = ($config | get --optional ttl_sops | default "900")
|
|
print (" Final Config: " + ($ttl_final | into string) + "s (5 minutes)")
|
|
print (" KCL Compilation: " + ($ttl_kcl | into string) + "s (30 minutes)")
|
|
print (" SOPS Decryption: " + ($ttl_sops | into string) + "s (15 minutes)")
|
|
print " Provider Config: 600s (10 minutes)"
|
|
print " Platform Config: 600s (10 minutes)"
|
|
print ""
|
|
|
|
print "▸ Security Settings:"
|
|
print " SOPS File Permissions: 0600 (owner read-only)"
|
|
print " SOPS Directory Permissions: 0700 (owner access only)"
|
|
print ""
|
|
|
|
print "▸ Validation Settings:"
|
|
print " Strict mtime Checking: true (validates all source files)"
|
|
print ""
|
|
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
print ""
|
|
}
|
|
"get" => {
|
|
if ($args | length) > 1 {
|
|
let setting = $args | get 1
|
|
let value = (cache-config-get $setting)
|
|
if $value != null {
|
|
print $"($setting) = ($value)"
|
|
} else {
|
|
print $"Setting not found: ($setting)"
|
|
}
|
|
} else {
|
|
print "❌ cache config get requires a setting path"
|
|
print "Usage: provisioning cache config get <path>"
|
|
exit 1
|
|
}
|
|
}
|
|
"set" => {
|
|
if ($args | length) > 2 {
|
|
let setting = $args | get 1
|
|
let value = ($args | skip 2 | str join " ")
|
|
cache-config-set $setting $value
|
|
print $"✓ Set ($setting) = ($value)"
|
|
} else {
|
|
print "❌ cache config set requires setting path and value"
|
|
print "Usage: provisioning cache config set <path> <value>"
|
|
exit 1
|
|
}
|
|
}
|
|
_ => {
|
|
print $"❌ Unknown cache config subcommand: ($config_cmd)"
|
|
print ""
|
|
print "Available cache config subcommands:"
|
|
print " show - Show all cache configuration"
|
|
print " get <setting> - Get specific cache setting"
|
|
print " set <key> <val> - Set cache setting"
|
|
print ""
|
|
print "Available settings for get/set:"
|
|
print " enabled - Cache enabled (true/false)"
|
|
print " ttl_final_config - TTL for final config (seconds)"
|
|
print " ttl_kcl - TTL for KCL compilation (seconds)"
|
|
print " ttl_sops - TTL for SOPS decryption (seconds)"
|
|
print ""
|
|
print "Examples:"
|
|
print " provisioning cache config show"
|
|
print " provisioning cache config get ttl_final_config"
|
|
print " provisioning cache config set ttl_final_config 600"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
"clear" => {
|
|
let cache_type = if ($args | length) > 0 { $args | get 0 } else { "all" }
|
|
cache-clear $cache_type
|
|
print $"✓ Cleared cache: ($cache_type)"
|
|
}
|
|
|
|
"list" => {
|
|
let cache_type = if ($args | length) > 0 { $args | get 0 } else { "*" }
|
|
let items = (cache-list $cache_type)
|
|
if ($items | length) > 0 {
|
|
print $"Cache items \(type: ($cache_type)\):"
|
|
$items | each { |item| print $" ($item)" }
|
|
} else {
|
|
print "No cache items found"
|
|
}
|
|
}
|
|
|
|
"help" => {
|
|
print "
|
|
Cache Management Commands:
|
|
|
|
provisioning cache status # Show cache status and statistics
|
|
provisioning cache config show # Show cache configuration
|
|
provisioning cache config get <setting> # Get specific cache setting
|
|
provisioning cache config set <setting> <val> # Set cache setting
|
|
provisioning cache clear [type] # Clear cache (default: all)
|
|
provisioning cache list [type] # List cached items (default: all)
|
|
provisioning cache help # Show this help message
|
|
|
|
Available settings (for get/set):
|
|
enabled - Cache enabled (true/false)
|
|
ttl_final_config - TTL for final config (seconds)
|
|
ttl_kcl - TTL for KCL compilation (seconds)
|
|
ttl_sops - TTL for SOPS decryption (seconds)
|
|
|
|
Examples:
|
|
provisioning cache status
|
|
provisioning cache config get ttl_final_config
|
|
provisioning cache config set ttl_final_config 600
|
|
provisioning cache config set enabled false
|
|
provisioning cache clear kcl
|
|
provisioning cache list
|
|
"
|
|
}
|
|
|
|
_ => {
|
|
print $"❌ Unknown cache command: ($subcommand)"
|
|
print ""
|
|
print "Available cache commands:"
|
|
print " status - Show cache status and statistics"
|
|
print " config show - Show cache configuration"
|
|
print " config get <key> - Get specific cache setting"
|
|
print " config set <k> <v> - Set cache setting"
|
|
print " clear [type] - Clear cache (all, kcl, sops, final)"
|
|
print " list [type] - List cached items"
|
|
print " help - Show this help message"
|
|
print ""
|
|
print "Examples:"
|
|
print " provisioning cache status"
|
|
print " provisioning cache config get ttl_final_config"
|
|
print " provisioning cache config set ttl_final_config 600"
|
|
print " provisioning cache clear kcl"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# Providers command handler - supports list, info, install, remove, installed, validate
|
|
def handle_providers [ops: string, flags: record] {
|
|
use ../../lib_provisioning/kcl_module_loader.nu *
|
|
|
|
# Parse subcommand and arguments
|
|
let parts = if ($ops | is-not-empty) {
|
|
($ops | str trim | split row " " | where { |x| ($x | is-not-empty) })
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
let subcommand = if ($parts | length) > 0 { $parts | get 0 } else { "list" }
|
|
let args = if ($parts | length) > 1 { $parts | skip 1 } else { [] }
|
|
|
|
match $subcommand {
|
|
"list" => { handle_providers_list $flags $args }
|
|
"info" => { handle_providers_info $args $flags }
|
|
"install" => { handle_providers_install $args $flags }
|
|
"remove" => { handle_providers_remove $args $flags }
|
|
"installed" => { handle_providers_installed $args $flags }
|
|
"validate" => { handle_providers_validate $args $flags }
|
|
"help" | "-h" | "--help" => { show_providers_help }
|
|
_ => {
|
|
print $"❌ Unknown providers subcommand: ($subcommand)"
|
|
print ""
|
|
show_providers_help
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# List all available providers
|
|
def handle_providers_list [flags: record, args: list] {
|
|
use ../../lib_provisioning/kcl_module_loader.nu *
|
|
|
|
_print $"(_ansi green)PROVIDERS(_ansi reset) list: \n"
|
|
|
|
# Parse flags
|
|
let show_kcl = ($args | any { |x| $x == "--kcl" })
|
|
let format_idx = ($args | enumerate | where item == "--format" | get 0?.index | default (-1))
|
|
let format = if $format_idx >= 0 and ($args | length) > ($format_idx + 1) {
|
|
$args | get ($format_idx + 1)
|
|
} else {
|
|
"table"
|
|
}
|
|
let no_cache = ($args | any { |x| $x == "--no-cache" })
|
|
|
|
# Get providers using cached KCL module loader
|
|
let providers = if $no_cache {
|
|
(discover-kcl-modules "providers")
|
|
} else {
|
|
(discover-kcl-modules-cached "providers")
|
|
}
|
|
|
|
match $format {
|
|
"json" => {
|
|
_print ($providers | to json) "json" "result" "table"
|
|
}
|
|
"yaml" => {
|
|
_print ($providers | to yaml) "yaml" "result" "table"
|
|
}
|
|
_ => {
|
|
# Table format - show summary or full with --kcl
|
|
if $show_kcl {
|
|
_print ($providers | to json) "json" "result" "table"
|
|
} else {
|
|
# Show simplified table
|
|
let simplified = ($providers | each {|p|
|
|
{name: $p.name, type: $p.type, version: $p.version}
|
|
})
|
|
_print ($simplified | to json) "json" "result" "table"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Show detailed provider information
|
|
def handle_providers_info [args: list, flags: record] {
|
|
use ../../lib_provisioning/kcl_module_loader.nu *
|
|
|
|
if ($args | is-empty) {
|
|
print "❌ Provider name required"
|
|
print "Usage: provisioning providers info <provider> [--kcl] [--no-cache]"
|
|
exit 1
|
|
}
|
|
|
|
let provider_name = $args | get 0
|
|
let show_kcl = ($args | any { |x| $x == "--kcl" })
|
|
let no_cache = ($args | any { |x| $x == "--no-cache" })
|
|
|
|
print $"(_ansi blue_bold)📋 Provider Information: ($provider_name)(_ansi reset)"
|
|
print ""
|
|
|
|
let providers = if $no_cache {
|
|
(discover-kcl-modules "providers")
|
|
} else {
|
|
(discover-kcl-modules-cached "providers")
|
|
}
|
|
let provider_info = ($providers | where name == $provider_name)
|
|
|
|
if ($provider_info | is-empty) {
|
|
print $"❌ Provider not found: ($provider_name)"
|
|
exit 1
|
|
}
|
|
|
|
let info = ($provider_info | first)
|
|
|
|
print $" Name: ($info.name)"
|
|
print $" Type: ($info.type)"
|
|
print $" Path: ($info.path)"
|
|
print $" Has KCL: ($info.has_kcl)"
|
|
|
|
if $show_kcl and $info.has_kcl {
|
|
print ""
|
|
print " (_ansi cyan_bold)KCL Module:(_ansi reset)"
|
|
print $" Module Name: ($info.kcl_module_name)"
|
|
print $" KCL Path: ($info.kcl_path)"
|
|
print $" Version: ($info.version)"
|
|
print $" Edition: ($info.edition)"
|
|
|
|
# Check for kcl.mod file
|
|
let kcl_mod = ($info.kcl_path | path join "kcl.mod")
|
|
if ($kcl_mod | path exists) {
|
|
print ""
|
|
print $" (_ansi cyan_bold)kcl.mod content:(_ansi reset)"
|
|
open $kcl_mod | lines | each {|line| print $" ($line)"}
|
|
}
|
|
}
|
|
|
|
print ""
|
|
}
|
|
|
|
# Install provider for infrastructure
|
|
def handle_providers_install [args: list, flags: record] {
|
|
use ../../lib_provisioning/kcl_module_loader.nu *
|
|
|
|
if ($args | length) < 2 {
|
|
print "❌ Provider name and infrastructure required"
|
|
print "Usage: provisioning providers install <provider> <infra> [--version <v>]"
|
|
exit 1
|
|
}
|
|
|
|
let provider_name = $args | get 0
|
|
let infra_name = $args | get 1
|
|
|
|
# Extract version flag if present
|
|
let version_idx = ($args | enumerate | where item == "--version" | get 0?.index | default (-1))
|
|
let version = if $version_idx >= 0 and ($args | length) > ($version_idx + 1) {
|
|
$args | get ($version_idx + 1)
|
|
} else {
|
|
"0.0.1"
|
|
}
|
|
|
|
# Resolve infrastructure path
|
|
let infra_path = (resolve_infra_path $infra_name)
|
|
|
|
if ($infra_path | is-empty) {
|
|
print $"❌ Infrastructure not found: ($infra_name)"
|
|
exit 1
|
|
}
|
|
|
|
# Install provider
|
|
install-provider $provider_name $infra_path --version $version
|
|
|
|
print ""
|
|
print $"(_ansi yellow_bold)💡 Next steps:(_ansi reset)"
|
|
print $" 1. Check the manifest: ($infra_path)/providers.manifest.yaml"
|
|
print $" 2. Update server definitions to use ($provider_name)"
|
|
print $" 3. Run: kcl run defs/servers.k"
|
|
}
|
|
|
|
# Remove provider from infrastructure
|
|
def handle_providers_remove [args: list, flags: record] {
|
|
use ../../lib_provisioning/kcl_module_loader.nu *
|
|
|
|
if ($args | length) < 2 {
|
|
print "❌ Provider name and infrastructure required"
|
|
print "Usage: provisioning providers remove <provider> <infra> [--force]"
|
|
exit 1
|
|
}
|
|
|
|
let provider_name = $args | get 0
|
|
let infra_name = $args | get 1
|
|
let force = ($args | any { |x| $x == "--force" })
|
|
|
|
# Resolve infrastructure path
|
|
let infra_path = (resolve_infra_path $infra_name)
|
|
|
|
if ($infra_path | is-empty) {
|
|
print $"❌ Infrastructure not found: ($infra_name)"
|
|
exit 1
|
|
}
|
|
|
|
# Confirmation unless forced
|
|
if not $force {
|
|
print $"(_ansi yellow)⚠️ This will remove provider ($provider_name) from ($infra_name)(_ansi reset)"
|
|
print " KCL dependencies will be updated."
|
|
let response = (input "Continue? (y/N): ")
|
|
|
|
if ($response | str downcase) != "y" {
|
|
print "❌ Cancelled"
|
|
return
|
|
}
|
|
}
|
|
|
|
# Remove provider
|
|
remove-provider $provider_name $infra_path
|
|
}
|
|
|
|
# List installed providers for infrastructure
|
|
def handle_providers_installed [args: list, flags: record] {
|
|
if ($args | is-empty) {
|
|
print "❌ Infrastructure name required"
|
|
print "Usage: provisioning providers installed <infra> [--format <fmt>]"
|
|
exit 1
|
|
}
|
|
|
|
let infra_name = $args | get 0
|
|
|
|
# Parse format flag
|
|
let format_idx = ($args | enumerate | where item == "--format" | get 0?.index | default (-1))
|
|
let format = if $format_idx >= 0 and ($args | length) > ($format_idx + 1) {
|
|
$args | get ($format_idx + 1)
|
|
} else {
|
|
"table"
|
|
}
|
|
|
|
# Resolve infrastructure path
|
|
let infra_path = (resolve_infra_path $infra_name)
|
|
|
|
if ($infra_path | is-empty) {
|
|
print $"❌ Infrastructure not found: ($infra_name)"
|
|
exit 1
|
|
}
|
|
|
|
let manifest_path = ($infra_path | path join "providers.manifest.yaml")
|
|
|
|
if not ($manifest_path | path exists) {
|
|
print $"❌ No providers.manifest.yaml found in ($infra_name)"
|
|
exit 1
|
|
}
|
|
|
|
let manifest = (open $manifest_path)
|
|
let providers = if ($manifest | get providers? | is-not-empty) {
|
|
$manifest | get providers
|
|
} else if ($manifest | get loaded_providers? | is-not-empty) {
|
|
$manifest | get loaded_providers
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
print $"(_ansi blue_bold)📦 Installed providers for ($infra_name):(_ansi reset)"
|
|
print ""
|
|
|
|
match $format {
|
|
"json" => {
|
|
_print ($providers | to json) "json" "result" "table"
|
|
}
|
|
"yaml" => {
|
|
_print ($providers | to yaml) "yaml" "result" "table"
|
|
}
|
|
_ => {
|
|
_print ($providers | to json) "json" "result" "table"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate provider installation
|
|
def handle_providers_validate [args: list, flags: record] {
|
|
use ../../lib_provisioning/kcl_module_loader.nu *
|
|
|
|
if ($args | is-empty) {
|
|
print "❌ Infrastructure name required"
|
|
print "Usage: provisioning providers validate <infra> [--no-cache]"
|
|
exit 1
|
|
}
|
|
|
|
let infra_name = $args | get 0
|
|
let no_cache = ($args | any { |x| $x == "--no-cache" })
|
|
|
|
print $"(_ansi blue_bold)🔍 Validating providers for ($infra_name)...(_ansi reset)"
|
|
print ""
|
|
|
|
# Resolve infrastructure path
|
|
let infra_path = (resolve_infra_path $infra_name)
|
|
|
|
if ($infra_path | is-empty) {
|
|
print $"❌ Infrastructure not found: ($infra_name)"
|
|
exit 1
|
|
}
|
|
|
|
mut validation_errors = []
|
|
|
|
# Check manifest exists
|
|
let manifest_path = ($infra_path | path join "providers.manifest.yaml")
|
|
if not ($manifest_path | path exists) {
|
|
$validation_errors = ($validation_errors | append "providers.manifest.yaml not found")
|
|
} else {
|
|
# Check each provider in manifest
|
|
let manifest = (open $manifest_path)
|
|
let providers = ($manifest | get providers? | default [])
|
|
|
|
# Load providers once using cache
|
|
let all_providers = if $no_cache {
|
|
(discover-kcl-modules "providers")
|
|
} else {
|
|
(discover-kcl-modules-cached "providers")
|
|
}
|
|
|
|
for provider in $providers {
|
|
print $" Checking ($provider.name)..."
|
|
|
|
# Check if provider exists in cached list
|
|
let available = ($all_providers | where name == $provider.name)
|
|
|
|
if ($available | is-empty) {
|
|
$validation_errors = ($validation_errors | append $"Provider not found: ($provider.name)")
|
|
print $" ❌ Not found in extensions"
|
|
} else {
|
|
let provider_info = ($available | first)
|
|
|
|
# Check if symlink exists
|
|
let modules_dir = ($infra_path | path join ".kcl-modules")
|
|
let link_path = ($modules_dir | path join $provider_info.kcl_module_name)
|
|
|
|
if not ($link_path | path exists) {
|
|
$validation_errors = ($validation_errors | append $"Symlink missing: ($link_path)")
|
|
print $" ❌ Symlink not found"
|
|
} else {
|
|
print $" ✓ OK"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check kcl.mod
|
|
let kcl_mod_path = ($infra_path | path join "kcl.mod")
|
|
if not ($kcl_mod_path | path exists) {
|
|
$validation_errors = ($validation_errors | append "kcl.mod not found")
|
|
}
|
|
|
|
print ""
|
|
|
|
# Report results
|
|
if ($validation_errors | is-empty) {
|
|
print "(_ansi green)✅ Validation passed - all providers correctly installed(_ansi reset)"
|
|
} else {
|
|
print "(_ansi red)❌ Validation failed:(_ansi reset)"
|
|
for error in $validation_errors {
|
|
print $" • ($error)"
|
|
}
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Helper: Resolve infrastructure path
|
|
def resolve_infra_path [infra: string]: nothing -> string {
|
|
if ($infra | path exists) {
|
|
return $infra
|
|
}
|
|
|
|
# Try workspace/infra path
|
|
let workspace_path = $"workspace/infra/($infra)"
|
|
if ($workspace_path | path exists) {
|
|
return $workspace_path
|
|
}
|
|
|
|
# Try absolute workspace path
|
|
let proj_root = ($env.PROVISIONING_ROOT? | default "/Users/Akasha/project-provisioning")
|
|
let abs_workspace_path = ($proj_root | path join "workspace" "infra" $infra)
|
|
if ($abs_workspace_path | path exists) {
|
|
return $abs_workspace_path
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
# Show providers help
|
|
def show_providers_help [] {
|
|
print $"
|
|
(_ansi cyan_bold)╔══════════════════════════════════════════════════╗(_ansi reset)
|
|
(_ansi cyan_bold)║(_ansi reset) 📦 PROVIDER MANAGEMENT (_ansi cyan_bold)║(_ansi reset)
|
|
(_ansi cyan_bold)╚══════════════════════════════════════════════════╝(_ansi reset)
|
|
|
|
(_ansi green_bold)[Available Providers](_ansi reset)
|
|
(_ansi blue)provisioning providers list [--kcl] [--format <fmt>](_ansi reset)
|
|
List all available providers
|
|
Formats: table (default value), json, yaml
|
|
|
|
(_ansi blue)provisioning providers info <provider> [--kcl](_ansi reset)
|
|
Show detailed provider information with optional KCL details
|
|
|
|
(_ansi green_bold)[Provider Installation](_ansi reset)
|
|
(_ansi blue)provisioning providers install <provider> <infra> [--version <v>](_ansi reset)
|
|
Install provider for an infrastructure
|
|
Default version: 0.0.1
|
|
|
|
(_ansi blue)provisioning providers remove <provider> <infra> [--force](_ansi reset)
|
|
Remove provider from infrastructure
|
|
--force skips confirmation prompt
|
|
|
|
(_ansi blue)provisioning providers installed <infra> [--format <fmt>](_ansi reset)
|
|
List installed providers for infrastructure
|
|
Formats: table (default value), json, yaml
|
|
|
|
(_ansi blue)provisioning providers validate <infra>(_ansi reset)
|
|
Validate provider installation and configuration
|
|
|
|
(_ansi green_bold)EXAMPLES(_ansi reset)
|
|
|
|
# List all providers
|
|
provisioning providers list
|
|
|
|
# Show KCL module details
|
|
provisioning providers info upcloud --kcl
|
|
|
|
# Install provider
|
|
provisioning providers install upcloud myinfra
|
|
|
|
# List installed providers
|
|
provisioning providers installed myinfra
|
|
|
|
# Validate installation
|
|
provisioning providers validate myinfra
|
|
|
|
# Remove provider
|
|
provisioning providers remove aws myinfra --force
|
|
|
|
(_ansi default_dimmed)💡 Use 'provisioning help providers' for more information(_ansi reset)
|
|
"
|
|
}
|
|
|
|
# Nu shell command handler
|
|
def handle_nu [ops: string, flags: record] {
|
|
let run_ops = if ($ops | str trim | str starts-with "-") {
|
|
""
|
|
} else {
|
|
let parts = ($ops | split row " ")
|
|
if ($parts | is-empty) { "" } else { $parts | first }
|
|
}
|
|
|
|
if ($flags.infra | is-not-empty) and ($env.PROVISIONING_INFRA_PATH | path join $flags.infra | path exists) {
|
|
cd ($env.PROVISIONING_INFRA_PATH | path join $flags.infra)
|
|
}
|
|
|
|
if ($flags.output_format | is-empty) {
|
|
if ($run_ops | is-empty) {
|
|
print (
|
|
$"\nTo exit (_ansi purple_bold)NuShell(_ansi reset) session, with (_ansi default_dimmed)lib_provisioning(_ansi reset) loaded, " +
|
|
$"use (_ansi green_bold)exit(_ansi reset) or (_ansi green_bold)[CTRL-D](_ansi reset)"
|
|
)
|
|
# Pass the provisioning configuration files to the Nu subprocess
|
|
# This ensures the interactive session has the same config loaded as the calling environment
|
|
let config_path = ($env.PROVISIONING_CONFIG? | default "")
|
|
# Build library paths argument - needed for module resolution during parsing
|
|
# Convert colon-separated string to -I flag arguments
|
|
let lib_dirs = ($env.NU_LIB_DIRS? | default "")
|
|
let lib_paths = if ($lib_dirs | is-not-empty) {
|
|
($lib_dirs | split row ":" | where { |x| ($x | is-not-empty) })
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
if ($config_path | is-not-empty) {
|
|
# Pass config files AND library paths via -I flags for module resolution
|
|
# Library paths are set via -I flags which enables module resolution during parsing phase
|
|
if ($lib_paths | length) > 0 {
|
|
# Construct command with -I flags for each library path
|
|
let cmd = (mut cmd_parts = []; for path in $lib_paths { $cmd_parts = ($cmd_parts | append "-I" | append $path) }; $cmd_parts)
|
|
# Start interactive Nushell with provisioning configuration loaded
|
|
# The -i flag enables interactive mode (REPL) with full terminal features
|
|
^nu --config $"($config_path)/config.nu" --env-config $"($config_path)/env.nu" ...$cmd -i
|
|
} else {
|
|
^nu --config $"($config_path)/config.nu" --env-config $"($config_path)/env.nu" -i
|
|
}
|
|
} else {
|
|
# Fallback if PROVISIONING_CONFIG not set
|
|
if ($lib_paths | length) > 0 {
|
|
let cmd = (mut cmd_parts = []; for path in $lib_paths { $cmd_parts = ($cmd_parts | append "-I" | append $path) }; $cmd_parts)
|
|
^nu ...$cmd -i
|
|
} else {
|
|
^nu -i
|
|
}
|
|
}
|
|
} else {
|
|
# Also pass library paths for single command execution
|
|
let lib_dirs = ($env.NU_LIB_DIRS? | default "")
|
|
let lib_paths = if ($lib_dirs | is-not-empty) {
|
|
($lib_dirs | split row ":" | where { |x| ($x | is-not-empty) })
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
if ($lib_paths | length) > 0 {
|
|
let cmd = (mut cmd_parts = []; for path in $lib_paths { $cmd_parts = ($cmd_parts | append "-I" | append $path) }; $cmd_parts)
|
|
^nu ...$cmd -c $"($run_ops)"
|
|
} else {
|
|
^nu -c $"($run_ops)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# List command handler
|
|
def handle_list [ops: string, flags: record] {
|
|
let target_list = if ($ops | is-not-empty) {
|
|
let parts = ($ops | split row " ")
|
|
if ($parts | is-empty) { "" } else { $parts | first }
|
|
} else { "" }
|
|
|
|
let list_ops = ($ops | str replace $"($target_list) " "" | str trim)
|
|
on_list $target_list ($flags.onsel | default "") $list_ops
|
|
}
|
|
|
|
# QR code command handler
|
|
def handle_qr [] {
|
|
make_qr
|
|
}
|
|
|
|
# Nu info command handler
|
|
def handle_nuinfo [] {
|
|
print $"\n (_ansi yellow)Nu shell info(_ansi reset)"
|
|
print (version)
|
|
}
|
|
|
|
# Plugins command handler
|
|
def handle_plugins [ops: string, flags: record] {
|
|
let subcommand = if ($ops | is-not-empty) {
|
|
($ops | split row " " | get 0)
|
|
} else {
|
|
"list"
|
|
}
|
|
|
|
let remaining_ops = if ($ops | is-not-empty) {
|
|
($ops | split row " " | skip 1 | str join " ")
|
|
} else {
|
|
""
|
|
}
|
|
|
|
match $subcommand {
|
|
"list" | "ls" => { handle_plugin_list $flags }
|
|
"register" | "add" => { handle_plugin_register $remaining_ops $flags }
|
|
"test" => { handle_plugin_test $remaining_ops $flags }
|
|
"build" => { handle_plugin_build $remaining_ops $flags }
|
|
"status" => { handle_plugin_status $flags }
|
|
"help" => { show_plugin_help }
|
|
_ => {
|
|
print $"❌ Unknown plugin subcommand: ($subcommand)"
|
|
print "Use 'provisioning plugin help' for available commands"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# List installed plugins with status
|
|
def handle_plugin_list [flags: record] {
|
|
use ../../lib_provisioning/plugins/mod.nu [list-plugins]
|
|
|
|
print $"\n (_ansi cyan_bold)Installed Plugins(_ansi reset)\n"
|
|
|
|
let plugins = (list-plugins)
|
|
|
|
if ($plugins | length) > 0 {
|
|
print ($plugins | table -e)
|
|
} else {
|
|
print "(_ansi yellow)No plugins found(_ansi reset)"
|
|
}
|
|
|
|
print $"\n(_ansi default_dimmed)💡 Use 'provisioning plugin register <name>' to register a plugin(_ansi reset)"
|
|
}
|
|
|
|
# Register plugin with Nushell
|
|
def handle_plugin_register [ops: string, flags: record] {
|
|
use ../../lib_provisioning/plugins/mod.nu [register-plugin]
|
|
|
|
let plugin_name = if ($ops | is-not-empty) {
|
|
($ops | split row " " | get 0)
|
|
} else {
|
|
print $"(_ansi red)❌ Plugin name required(_ansi reset)"
|
|
print $"Usage: provisioning plugin register <plugin_name>"
|
|
exit 1
|
|
}
|
|
|
|
register-plugin $plugin_name
|
|
}
|
|
|
|
# Test plugin functionality
|
|
def handle_plugin_test [ops: string, flags: record] {
|
|
use ../../lib_provisioning/plugins/mod.nu [test-plugin]
|
|
|
|
let plugin_name = if ($ops | is-not-empty) {
|
|
($ops | split row " " | get 0)
|
|
} else {
|
|
print $"(_ansi red)❌ Plugin name required(_ansi reset)"
|
|
print $"Usage: provisioning plugin test <plugin_name>"
|
|
print $"Valid plugins: auth, kms, tera, kcl"
|
|
exit 1
|
|
}
|
|
|
|
test-plugin $plugin_name
|
|
}
|
|
|
|
# Build plugins from source
|
|
def handle_plugin_build [ops: string, flags: record] {
|
|
use ../../lib_provisioning/plugins/mod.nu [build-plugins]
|
|
|
|
let plugin_name = if ($ops | is-not-empty) {
|
|
($ops | split row " " | get 0)
|
|
} else {
|
|
""
|
|
}
|
|
|
|
if ($plugin_name | is-empty) {
|
|
print $"\n(_ansi cyan)Building all plugins...(_ansi reset)"
|
|
build-plugins
|
|
} else {
|
|
print $"\n(_ansi cyan)Building plugin: ($plugin_name)(_ansi reset)"
|
|
build-plugins --plugin $plugin_name
|
|
}
|
|
}
|
|
|
|
# Show plugin status
|
|
def handle_plugin_status [flags: record] {
|
|
use ../../lib_provisioning/plugins/mod.nu [plugin-build-info]
|
|
use ../../lib_provisioning/plugins/auth.nu [plugin-auth-status]
|
|
use ../../lib_provisioning/plugins/kms.nu [plugin-kms-info]
|
|
|
|
print $"\n(_ansi cyan_bold)Plugin Status(_ansi reset)\n"
|
|
|
|
print $"(_ansi yellow_bold)Authentication Plugin:(_ansi reset)"
|
|
let auth_status = (plugin-auth-status)
|
|
print $" Available: ($auth_status.plugin_available)"
|
|
print $" Enabled: ($auth_status.plugin_enabled)"
|
|
print $" Mode: ($auth_status.mode)"
|
|
|
|
print $"\n(_ansi yellow_bold)KMS Plugin:(_ansi reset)"
|
|
let kms_info = (plugin-kms-info)
|
|
print $" Available: ($kms_info.plugin_available)"
|
|
print $" Enabled: ($kms_info.plugin_enabled)"
|
|
print $" Backend: ($kms_info.default_backend)"
|
|
print $" Mode: ($kms_info.mode)"
|
|
|
|
print $"\n(_ansi yellow_bold)Build Information:(_ansi reset)"
|
|
let build_info = (plugin-build-info)
|
|
if $build_info.exists {
|
|
print $" Source directory: ($build_info.plugins_dir)"
|
|
print $" Available sources: ($build_info.available_sources | length)"
|
|
} else {
|
|
print $" Source directory: Not found"
|
|
}
|
|
}
|
|
|
|
# Show plugin help
|
|
def show_plugin_help [] {
|
|
print $"
|
|
(_ansi cyan_bold)╔══════════════════════════════════════════════════╗(_ansi reset)
|
|
(_ansi cyan_bold)║(_ansi reset) 🔌 PLUGIN MANAGEMENT (_ansi cyan_bold)║(_ansi reset)
|
|
(_ansi cyan_bold)╚══════════════════════════════════════════════════╝(_ansi reset)
|
|
|
|
(_ansi green_bold)[Plugin Operations](_ansi reset)
|
|
(_ansi blue)plugin list(_ansi reset) List all plugins with status
|
|
(_ansi blue)plugin register <name>(_ansi reset) Register plugin with Nushell
|
|
(_ansi blue)plugin test <name>(_ansi reset) Test plugin functionality
|
|
(_ansi blue)plugin build [name](_ansi reset) Build plugins from source
|
|
(_ansi blue)plugin status(_ansi reset) Show plugin status and info
|
|
|
|
(_ansi green_bold)[Available Plugins](_ansi reset)
|
|
• (_ansi cyan)auth(_ansi reset) - JWT authentication with MFA support
|
|
• (_ansi cyan)kms(_ansi reset) - Key Management Service integration
|
|
• (_ansi cyan)tera(_ansi reset) - Template rendering engine
|
|
• (_ansi cyan)kcl(_ansi reset) - KCL configuration language
|
|
|
|
(_ansi green_bold)EXAMPLES(_ansi reset)
|
|
|
|
# List all plugins
|
|
provisioning plugin list
|
|
|
|
# Register auth plugin
|
|
provisioning plugin register nu_plugin_auth
|
|
|
|
# Test KMS plugin
|
|
provisioning plugin test kms
|
|
|
|
# Build all plugins
|
|
provisioning plugin build
|
|
|
|
# Build specific plugin
|
|
provisioning plugin build nu_plugin_auth
|
|
|
|
# Show plugin status
|
|
provisioning plugin status
|
|
|
|
(_ansi default_dimmed)💡 Plugins provide HTTP fallback when not registered
|
|
Authentication and KMS work in both plugin and HTTP modes(_ansi reset)
|
|
"
|
|
}
|
|
|
|
# Guide command handler
|
|
def handle_guide [ops: string, flags: record] {
|
|
let guide_topic = if ($ops | is-not-empty) {
|
|
($ops | split row " " | get 0)
|
|
} else {
|
|
""
|
|
}
|
|
|
|
# Define guide topics and their paths
|
|
let guides = {
|
|
"quickstart": "docs/guides/quickstart-cheatsheet.md",
|
|
"from-scratch": "docs/guides/from-scratch.md",
|
|
"scratch": "docs/guides/from-scratch.md",
|
|
"start": "docs/guides/from-scratch.md",
|
|
"deploy": "docs/guides/from-scratch.md",
|
|
"list": "list_guides"
|
|
}
|
|
|
|
# Get docs directory
|
|
let docs_dir = ($env.PROVISIONING_PATH | path join "docs" "guides")
|
|
|
|
match $guide_topic {
|
|
"" => {
|
|
# Show guide list
|
|
show_guide_list $docs_dir
|
|
}
|
|
|
|
"list" => {
|
|
show_guide_list $docs_dir
|
|
}
|
|
|
|
_ => {
|
|
# Try to find and display guide
|
|
let guide_path = if ($guide_topic in ($guides | columns)) { $guides | get $guide_topic } else { null }
|
|
|
|
if ($guide_path == null or $guide_path == "list_guides") {
|
|
print $"(_ansi red)❌ Unknown guide:(_ansi reset) ($guide_topic)"
|
|
print ""
|
|
show_guide_list $docs_dir
|
|
exit 1
|
|
}
|
|
|
|
let full_path = ($env.PROVISIONING_PATH | path join $guide_path)
|
|
|
|
if not ($full_path | path exists) {
|
|
print $"(_ansi red)❌ Guide file not found:(_ansi reset) ($full_path)"
|
|
exit 1
|
|
}
|
|
|
|
# Display guide using best available viewer
|
|
display_guide $full_path $guide_topic
|
|
}
|
|
}
|
|
}
|
|
|
|
# Display guide using best available markdown viewer
|
|
def display_guide [
|
|
guide_path: path
|
|
topic: string
|
|
] {
|
|
print $"\n(_ansi cyan_bold)📖 Guide:(_ansi reset) ($topic)\n"
|
|
|
|
# Check for viewers in order of preference: glow, bat, less, cat
|
|
if (which glow | length) > 0 {
|
|
^glow $guide_path
|
|
} else if (which bat | length) > 0 {
|
|
^bat --style=plain --paging=always $guide_path
|
|
} else if (which less | length) > 0 {
|
|
^less $guide_path
|
|
} else {
|
|
open $guide_path
|
|
}
|
|
}
|
|
|
|
# Show list of available guides
|
|
def show_guide_list [docs_dir: path] {
|
|
print $"
|
|
(_ansi magenta_bold)╔══════════════════════════════════════════════════╗(_ansi reset)
|
|
(_ansi magenta_bold)║(_ansi reset) 📚 AVAILABLE GUIDES (_ansi magenta_bold)║(_ansi reset)
|
|
(_ansi magenta_bold)╚══════════════════════════════════════════════════╝(_ansi reset)
|
|
|
|
(_ansi green_bold)[Step-by-Step Guides](_ansi reset)
|
|
|
|
(_ansi blue)provisioning guide from-scratch(_ansi reset)
|
|
Complete deployment from zero to production
|
|
(_ansi default_dimmed)Shortcuts: scratch, start, deploy(_ansi reset)
|
|
|
|
(_ansi green_bold)[Quick References](_ansi reset)
|
|
|
|
(_ansi blue)provisioning guide quickstart(_ansi reset)
|
|
Command shortcuts and quick reference
|
|
(_ansi default_dimmed)Shortcuts: shortcuts, quick(_ansi reset)
|
|
|
|
(_ansi green_bold)USAGE(_ansi reset)
|
|
|
|
# View guide
|
|
provisioning guide <topic>
|
|
|
|
# List all guides
|
|
provisioning guide list
|
|
provisioning howto (_ansi default_dimmed)# shortcut(_ansi reset)
|
|
|
|
(_ansi green_bold)EXAMPLES(_ansi reset)
|
|
|
|
# Complete deployment guide
|
|
provisioning guide from-scratch
|
|
|
|
# Quick command reference
|
|
provisioning guide quickstart
|
|
|
|
(_ansi green_bold)VIEWING TIPS(_ansi reset)
|
|
|
|
• (_ansi cyan)Best experience:(_ansi reset) Install glow for beautiful rendering
|
|
(_ansi default_dimmed)brew install glow # macOS(_ansi reset)
|
|
|
|
• (_ansi cyan)Alternative:(_ansi reset) bat provides syntax highlighting
|
|
(_ansi default_dimmed)brew install bat # macOS(_ansi reset)
|
|
|
|
• (_ansi cyan)Fallback:(_ansi reset) less/cat work on all systems
|
|
|
|
(_ansi default_dimmed)💡 All guides provide copy-paste ready commands
|
|
Perfect for quick start and reference!(_ansi reset)
|
|
"
|
|
} |