491 lines
16 KiB
Text
491 lines
16 KiB
Text
# Provider Command Handlers
|
|
# Domain: Provider discovery, installation, removal, validation, and information
|
|
|
|
# REMOVED: use ../../../lib_provisioning * - causes circular import
|
|
use cli/flags.nu *
|
|
use primitives/io/interface.nu [_print _ansi]
|
|
|
|
# Validate identifier is safe from path/command injection
|
|
def validate_safe_identifier [id: string] {
|
|
# Returns true if INVALID (contains dangerous patterns)
|
|
let has_slash = ($id | str contains "/")
|
|
let has_dotdot = ($id | str contains "..")
|
|
let starts_slash = ($id | str starts-with "/")
|
|
let has_semicolon = ($id | str contains ";")
|
|
let has_pipe = ($id | str contains "|")
|
|
let has_ampersand = ($id | str contains "&")
|
|
let has_dollar = ($id | str contains "$")
|
|
let has_backtick = ($id | str contains "`")
|
|
|
|
if $has_slash or $has_dotdot or $starts_slash or $has_semicolon or $has_pipe or $has_ampersand or $has_dollar or $has_backtick {
|
|
return true
|
|
}
|
|
false
|
|
}
|
|
|
|
# Main providers command handler - Manage infrastructure providers
|
|
export def handle_providers [ops: string, flags: record] {
|
|
|
|
# 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] {
|
|
|
|
_print $"(_ansi green)PROVIDERS(_ansi reset) list: \n"
|
|
|
|
# Parse flags
|
|
let show_nickel = ($args | any { |x| $x == "--nickel" })
|
|
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 Nickel module loader
|
|
let providers = if $no_cache {
|
|
(discover-nickel-modules "providers")
|
|
} else {
|
|
(discover-nickel-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 --nickel
|
|
if $show_nickel {
|
|
_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] {
|
|
|
|
if ($args | is-empty) {
|
|
print "❌ Provider name required"
|
|
print "Usage: provisioning providers info <provider> [--nickel] [--no-cache]"
|
|
exit 1
|
|
}
|
|
|
|
let provider_name = $args | get 0
|
|
|
|
# Validate provider name
|
|
if (validate_safe_identifier $provider_name) {
|
|
error make { msg: "Invalid provider name - contains invalid characters" }
|
|
}
|
|
|
|
let show_nickel = ($args | any { |x| $x == "--nickel" })
|
|
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-nickel-modules "providers")
|
|
} else {
|
|
(discover-nickel-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 Nickel: ($info.has_nickel)"
|
|
|
|
if $show_nickel and $info.has_nickel {
|
|
print ""
|
|
print " (_ansi cyan_bold)Nickel Module:(_ansi reset)"
|
|
print $" Module Name: ($info.module_name)"
|
|
print $" Nickel Path: ($info.schema_path)"
|
|
print $" Version: ($info.version)"
|
|
print $" Edition: ($info.edition)"
|
|
|
|
# Check for nickel.mod file
|
|
let decl_mod = ($info.schema_path | path join "nickel.mod")
|
|
if ($decl_mod | path exists) {
|
|
print ""
|
|
print $" (_ansi cyan_bold)nickel.mod content:(_ansi reset)"
|
|
open $decl_mod | lines | each {|line| print $" ($line)"}
|
|
}
|
|
}
|
|
|
|
print ""
|
|
}
|
|
|
|
# Install provider for infrastructure
|
|
def handle_providers_install [args: list, flags: record] {
|
|
|
|
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
|
|
|
|
# Validate provider and infrastructure names
|
|
if (validate_safe_identifier $provider_name) {
|
|
error make { msg: "Invalid provider name - contains invalid characters" }
|
|
}
|
|
if (validate_safe_identifier $infra_name) {
|
|
error make { msg: "Invalid infrastructure name - contains invalid characters" }
|
|
}
|
|
|
|
# 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: nickel run defs/servers.ncl"
|
|
}
|
|
|
|
# Remove provider from infrastructure
|
|
def handle_providers_remove [args: list, flags: record] {
|
|
|
|
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
|
|
|
|
# Validate provider and infrastructure names
|
|
if (validate_safe_identifier $provider_name) {
|
|
error make { msg: "Invalid provider name - contains invalid characters" }
|
|
}
|
|
if (validate_safe_identifier $infra_name) {
|
|
error make { msg: "Invalid infrastructure name - contains invalid characters" }
|
|
}
|
|
|
|
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 " Nickel 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
|
|
|
|
# Validate infrastructure name
|
|
if (validate_safe_identifier $infra_name) {
|
|
error make { msg: "Invalid infrastructure name - contains invalid characters" }
|
|
}
|
|
|
|
# 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] {
|
|
|
|
if ($args | is-empty) {
|
|
print "❌ Infrastructure name required"
|
|
print "Usage: provisioning providers validate <infra> [--no-cache]"
|
|
exit 1
|
|
}
|
|
|
|
let infra_name = $args | get 0
|
|
|
|
# Validate infrastructure name
|
|
if (validate_safe_identifier $infra_name) {
|
|
error make { msg: "Invalid infrastructure name - contains invalid characters" }
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# Refactored from mutable to immutable accumulation (Rule 3)
|
|
let validation_result = (
|
|
# Check manifest exists
|
|
let manifest_path = ($infra_path | path join "providers.manifest.yaml");
|
|
let initial = {has_manifest: false, errors: []};
|
|
|
|
if not ($manifest_path | path exists) {
|
|
$initial | upsert has_manifest false | upsert errors ["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-nickel-modules "providers")
|
|
} else {
|
|
(discover-nickel-modules-cached "providers")
|
|
}
|
|
|
|
# Use reduce --fold to accumulate validation errors (Rule 3)
|
|
let validation = ($providers | reduce --fold {errors: []} {|provider, result|
|
|
print $" Checking ($provider.name)..."
|
|
|
|
# Check if provider exists in cached list
|
|
let available = ($all_providers | where name == $provider.name)
|
|
|
|
if ($available | is-empty) {
|
|
$result | upsert errors ($result.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 ".nickel-modules")
|
|
let link_path = ($modules_dir | path join $provider_info.module_name)
|
|
|
|
if not ($link_path | path exists) {
|
|
$result | upsert errors ($result.errors | append $"Symlink missing: ($link_path)")
|
|
print $" ❌ Symlink not found"
|
|
} else {
|
|
print $" ✓ OK"
|
|
$result
|
|
}
|
|
}
|
|
})
|
|
|
|
# Check nickel.mod
|
|
let nickel_mod_path = ($infra_path | path join "nickel.mod")
|
|
let final_errors = if not ($nickel_mod_path | path exists) {
|
|
($validation.errors | append "nickel.mod not found")
|
|
} else {
|
|
$validation.errors
|
|
}
|
|
|
|
$initial | upsert has_manifest true | upsert errors $final_errors
|
|
}
|
|
)
|
|
|
|
print ""
|
|
|
|
# Report results
|
|
if ($validation_result.errors | is-empty) {
|
|
print "(_ansi green)✅ Validation passed - all providers correctly installed(_ansi reset)"
|
|
} else {
|
|
print "(_ansi red)❌ Validation failed:(_ansi reset)"
|
|
$validation_result.errors | each {|error| print $" • ($error)"}
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Helper: Resolve infrastructure path
|
|
def resolve_infra_path [infra: 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 ($env.HOME | path join "Development/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 [--nickel] [--format <fmt>](_ansi reset)
|
|
List all available providers
|
|
Formats: table (default value), json, yaml
|
|
|
|
(_ansi blue)provisioning providers info <provider> [--nickel](_ansi reset)
|
|
Show detailed provider information with optional Nickel 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 Nickel module details
|
|
provisioning providers info upcloud --nickel
|
|
|
|
# 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)
|
|
"
|
|
}
|