chore: add current provisioning state before migration
This commit is contained in:
parent
a9703b4748
commit
50745b0f22
660 changed files with 88126 additions and 0 deletions
12
core/nulib/lib_provisioning/utils/clean.nu
Normal file
12
core/nulib/lib_provisioning/utils/clean.nu
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export def cleanup [
|
||||
wk_path: string
|
||||
]: nothing -> nothing {
|
||||
if $env.PROVISIONING_DEBUG == false and ($wk_path | path exists) {
|
||||
rm --force --recursive $wk_path
|
||||
} else {
|
||||
#use utils/interface.nu _ansi
|
||||
_print $"(_ansi default_dimmed)______________________(_ansi reset)"
|
||||
_print $"(_ansi default_dimmed)Work files not removed"
|
||||
_print $"(_ansi default_dimmed)wk_path:(_ansi reset) ($wk_path)"
|
||||
}
|
||||
}
|
||||
107
core/nulib/lib_provisioning/utils/config.nu
Normal file
107
core/nulib/lib_provisioning/utils/config.nu
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Enhanced configuration management for provisioning tool
|
||||
|
||||
export def load-config [
|
||||
config_path: string
|
||||
--validate = true
|
||||
]: record {
|
||||
if not ($config_path | path exists) {
|
||||
print $"🛑 Configuration file not found: ($config_path)"
|
||||
return {}
|
||||
}
|
||||
|
||||
try {
|
||||
let config = (open $config_path)
|
||||
if $validate {
|
||||
validate-config $config
|
||||
}
|
||||
$config
|
||||
} catch {|err|
|
||||
print $"🛑 Error loading configuration from ($config_path): ($err.msg)"
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
export def validate-config [
|
||||
config: record
|
||||
]: bool {
|
||||
let required_fields = ["version", "providers", "servers"]
|
||||
let missing_fields = ($required_fields | where {|field|
|
||||
($config | get -o $field | is-empty)
|
||||
})
|
||||
|
||||
if ($missing_fields | length) > 0 {
|
||||
print "🛑 Missing required configuration fields:"
|
||||
$missing_fields | each {|field| print $" - ($field)"}
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def merge-configs [
|
||||
base_config: record
|
||||
override_config: record
|
||||
]: record {
|
||||
$base_config | merge $override_config
|
||||
}
|
||||
|
||||
export def get-config-value [
|
||||
config: record
|
||||
path: string
|
||||
default_value?: any
|
||||
]: any {
|
||||
let path_parts = ($path | split row ".")
|
||||
let mut current = $config
|
||||
|
||||
for part in $path_parts {
|
||||
if ($current | get -o $part | is-empty) {
|
||||
return $default_value
|
||||
}
|
||||
$current = ($current | get $part)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
export def set-config-value [
|
||||
config: record
|
||||
path: string
|
||||
value: any
|
||||
]: record {
|
||||
let path_parts = ($path | split row ".")
|
||||
let mut result = $config
|
||||
|
||||
if ($path_parts | length) == 1 {
|
||||
$result | upsert $path_parts.0 $value
|
||||
} else {
|
||||
let key = ($path_parts | last)
|
||||
let parent_path = ($path_parts | range 0..-1 | str join ".")
|
||||
let parent = (get-config-value $result $parent_path {})
|
||||
let updated_parent = ($parent | upsert $key $value)
|
||||
set-config-value $result $parent_path $updated_parent
|
||||
}
|
||||
}
|
||||
|
||||
export def save-config [
|
||||
config: record
|
||||
config_path: string
|
||||
--backup = true
|
||||
]: bool {
|
||||
if $backup and ($config_path | path exists) {
|
||||
let backup_path = $"($config_path).backup.(date now | format date '%Y%m%d_%H%M%S')"
|
||||
try {
|
||||
cp $config_path $backup_path
|
||||
print $"💾 Backup created: ($backup_path)"
|
||||
} catch {|err|
|
||||
print $"⚠️ Warning: Could not create backup: ($err.msg)"
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$config | to yaml | save $config_path
|
||||
print $"✅ Configuration saved to: ($config_path)"
|
||||
true
|
||||
} catch {|err|
|
||||
print $"🛑 Error saving configuration: ($err.msg)"
|
||||
false
|
||||
}
|
||||
}
|
||||
88
core/nulib/lib_provisioning/utils/enhanced_logging.nu
Normal file
88
core/nulib/lib_provisioning/utils/enhanced_logging.nu
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Enhanced logging system for provisioning tool
|
||||
|
||||
export def log-info [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"ℹ️ ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
|
||||
export def log-success [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"✅ ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
|
||||
export def log-warning [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"⚠️ ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
|
||||
export def log-error [
|
||||
message: string
|
||||
context?: string
|
||||
details?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
let details_str = if ($details | is-not-empty) { $"\n Details: ($details)" } else { "" }
|
||||
print $"🛑 ($timestamp)($context_str) ($message)($details_str)"
|
||||
}
|
||||
|
||||
export def log-debug [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"🐛 ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
}
|
||||
|
||||
export def log-step [
|
||||
step: string
|
||||
total_steps: int
|
||||
current_step: int
|
||||
context?: string
|
||||
] {
|
||||
let progress = $"($current_step)/($total_steps)"
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"🔄 ($progress)($context_str) ($step)"
|
||||
}
|
||||
|
||||
export def log-progress [
|
||||
message: string
|
||||
percent: int
|
||||
context?: string
|
||||
] {
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"📊 ($context_str) ($message) ($percent)%"
|
||||
}
|
||||
|
||||
export def log-section [
|
||||
title: string
|
||||
context?: string
|
||||
] {
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $""
|
||||
print $"📋 ($context_str) ($title)"
|
||||
print $"─────────────────────────────────────────────────────────────"
|
||||
}
|
||||
|
||||
export def log-subsection [
|
||||
title: string
|
||||
context?: string
|
||||
] {
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $" 📌 ($context_str) ($title)"
|
||||
}
|
||||
78
core/nulib/lib_provisioning/utils/error.nu
Normal file
78
core/nulib/lib_provisioning/utils/error.nu
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
export def throw-error [
|
||||
error: string
|
||||
text?: string
|
||||
context?: string
|
||||
--span: record
|
||||
--code: int = 1
|
||||
--suggestion: string
|
||||
]: nothing -> nothing {
|
||||
#use utils/interface.nu _ansi
|
||||
let error = $"\n(_ansi red_bold)($error)(_ansi reset)"
|
||||
let msg = ($text | default "this caused an internal error")
|
||||
let suggestion = if ($suggestion | is-not-empty) { $"\n💡 Suggestion: (_ansi yellow)($suggestion)(_ansi reset)" } else { "" }
|
||||
|
||||
# Log error for debugging
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
print $"DEBUG: Error occurred at: (date now | format date '%Y-%m-%d %H:%M:%S')"
|
||||
print $"DEBUG: Context: ($context | default 'no context')"
|
||||
print $"DEBUG: Error code: ($code)"
|
||||
}
|
||||
|
||||
if ($env.PROVISIONING_OUT | is-empty) {
|
||||
if $span == null and $context == null {
|
||||
error make --unspanned { msg: ( $error + "\n" + $msg + $suggestion) }
|
||||
} else if $span != null and $env.PROVISIONING_METADATA {
|
||||
error make {
|
||||
msg: $error
|
||||
label: {
|
||||
text: $"($msg) (_ansi blue)($context)(_ansi reset)($suggestion)"
|
||||
span: $span
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error make --unspanned { msg: ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)") }
|
||||
}
|
||||
} else {
|
||||
_print ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
|
||||
export def safe-execute [
|
||||
command: closure
|
||||
context: string
|
||||
--fallback: closure
|
||||
] {
|
||||
let result = (do $command | complete)
|
||||
if $result.exit_code != 0 {
|
||||
print $"⚠️ Warning: Error in ($context): ($result.stderr)"
|
||||
if ($fallback | is-not-empty) {
|
||||
print "🔄 Executing fallback..."
|
||||
do $fallback
|
||||
} else {
|
||||
print $"🛑 Execution failed in ($context)"
|
||||
print $" Error: ($result.stderr)"
|
||||
}
|
||||
} else {
|
||||
$result.stdout
|
||||
}
|
||||
}
|
||||
|
||||
export def try [
|
||||
settings_data: record
|
||||
defaults_data: record
|
||||
]: nothing -> nothing {
|
||||
$settings_data.servers | each { |server|
|
||||
_print ( $defaults_data.defaults | merge $server )
|
||||
}
|
||||
_print ($settings_data.servers | get hostname)
|
||||
_print ($settings_data.servers | get 0).tasks
|
||||
let zli_cfg = (open "resources/oci-reg/zli-cfg" | from json)
|
||||
if $zli_cfg.sops? != null {
|
||||
_print "Found"
|
||||
} else {
|
||||
_print "NOT Found"
|
||||
}
|
||||
let pos = 0
|
||||
_print ($settings_data.servers | get $pos )
|
||||
}
|
||||
|
||||
81
core/nulib/lib_provisioning/utils/error_clean.nu
Normal file
81
core/nulib/lib_provisioning/utils/error_clean.nu
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
export def throw-error [
|
||||
error: string
|
||||
text?: string
|
||||
context?: string
|
||||
--span: record
|
||||
--code: int = 1
|
||||
--suggestion: string
|
||||
]: nothing -> nothing {
|
||||
let error = $"\n(_ansi red_bold)($error)(_ansi reset)"
|
||||
let msg = ($text | default "this caused an internal error")
|
||||
let suggestion = if ($suggestion | is-not-empty) {
|
||||
$"\n💡 Suggestion: (_ansi yellow)($suggestion)(_ansi reset)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
# Log error for debugging
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
print $"DEBUG: Error occurred at: (date now | format date '%Y-%m-%d %H:%M:%S')"
|
||||
print $"DEBUG: Context: ($context | default 'no context')"
|
||||
print $"DEBUG: Error code: ($code)"
|
||||
}
|
||||
|
||||
if ($env.PROVISIONING_OUT | is-empty) {
|
||||
if $span == null and $context == null {
|
||||
error make --unspanned { msg: ( $error + "\n" + $msg + $suggestion) }
|
||||
} else if $span != null and $env.PROVISIONING_METADATA {
|
||||
error make {
|
||||
msg: $error
|
||||
label: {
|
||||
text: $"($msg) (_ansi blue)($context)(_ansi reset)($suggestion)"
|
||||
span: $span
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error make --unspanned {
|
||||
msg: ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_print ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
|
||||
export def safe-execute [
|
||||
command: closure
|
||||
context: string
|
||||
--fallback: closure
|
||||
]: any {
|
||||
try {
|
||||
do $command
|
||||
} catch {|err|
|
||||
print $"⚠️ Warning: Error in ($context): ($err.msg)"
|
||||
if ($fallback | is-not-empty) {
|
||||
print "🔄 Executing fallback..."
|
||||
do $fallback
|
||||
} else {
|
||||
print $"🛑 Execution failed in ($context)"
|
||||
print $" Error: ($err.msg)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export def try [
|
||||
settings_data: record
|
||||
defaults_data: record
|
||||
]: nothing -> nothing {
|
||||
$settings_data.servers | each { |server|
|
||||
_print ( $defaults_data.defaults | merge $server )
|
||||
}
|
||||
_print ($settings_data.servers | get hostname)
|
||||
_print ($settings_data.servers | get 0).tasks
|
||||
let zli_cfg = (open "resources/oci-reg/zli-cfg" | from json)
|
||||
if $zli_cfg.sops? != null {
|
||||
_print "Found"
|
||||
} else {
|
||||
_print "NOT Found"
|
||||
}
|
||||
let pos = 0
|
||||
_print ($settings_data.servers | get $pos )
|
||||
}
|
||||
80
core/nulib/lib_provisioning/utils/error_final.nu
Normal file
80
core/nulib/lib_provisioning/utils/error_final.nu
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
export def throw-error [
|
||||
error: string
|
||||
text?: string
|
||||
context?: string
|
||||
--span: record
|
||||
--code: int = 1
|
||||
--suggestion: string
|
||||
]: nothing -> nothing {
|
||||
let error = $"\n(_ansi red_bold)($error)(_ansi reset)"
|
||||
let msg = ($text | default "this caused an internal error")
|
||||
let suggestion = if ($suggestion | is-not-empty) {
|
||||
$"\n💡 Suggestion: (_ansi yellow)($suggestion)(_ansi reset)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
print $"DEBUG: Error occurred at: (date now | format date '%Y-%m-%d %H:%M:%S')"
|
||||
print $"DEBUG: Context: ($context | default 'no context')"
|
||||
print $"DEBUG: Error code: ($code)"
|
||||
}
|
||||
|
||||
if ($env.PROVISIONING_OUT | is-empty) {
|
||||
if $span == null and $context == null {
|
||||
error make --unspanned { msg: ( $error + "\n" + $msg + $suggestion) }
|
||||
} else if $span != null and $env.PROVISIONING_METADATA {
|
||||
error make {
|
||||
msg: $error
|
||||
label: {
|
||||
text: $"($msg) (_ansi blue)($context)(_ansi reset)($suggestion)"
|
||||
span: $span
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error make --unspanned {
|
||||
msg: ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_print ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
|
||||
export def safe-execute [
|
||||
command: closure
|
||||
context: string
|
||||
--fallback: closure
|
||||
] {
|
||||
try {
|
||||
do $command
|
||||
} catch {|err|
|
||||
print $"⚠️ Warning: Error in ($context): ($err.msg)"
|
||||
if ($fallback | is-not-empty) {
|
||||
print "🔄 Executing fallback..."
|
||||
do $fallback
|
||||
} else {
|
||||
print $"🛑 Execution failed in ($context)"
|
||||
print $" Error: ($err.msg)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export def try [
|
||||
settings_data: record
|
||||
defaults_data: record
|
||||
]: nothing -> nothing {
|
||||
$settings_data.servers | each { |server|
|
||||
_print ( $defaults_data.defaults | merge $server )
|
||||
}
|
||||
_print ($settings_data.servers | get hostname)
|
||||
_print ($settings_data.servers | get 0).tasks
|
||||
let zli_cfg = (open "resources/oci-reg/zli-cfg" | from json)
|
||||
if $zli_cfg.sops? != null {
|
||||
_print "Found"
|
||||
} else {
|
||||
_print "NOT Found"
|
||||
}
|
||||
let pos = 0
|
||||
_print ($settings_data.servers | get $pos )
|
||||
}
|
||||
81
core/nulib/lib_provisioning/utils/error_fixed.nu
Normal file
81
core/nulib/lib_provisioning/utils/error_fixed.nu
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
export def throw-error [
|
||||
error: string
|
||||
text?: string
|
||||
context?: string
|
||||
--span: record
|
||||
--code: int = 1
|
||||
--suggestion: string
|
||||
]: nothing -> nothing {
|
||||
let error = $"\n(_ansi red_bold)($error)(_ansi reset)"
|
||||
let msg = ($text | default "this caused an internal error")
|
||||
let suggestion = if ($suggestion | is-not-empty) {
|
||||
$"\n💡 Suggestion: (_ansi yellow)($suggestion)(_ansi reset)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
# Log error for debugging
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
print $"DEBUG: Error occurred at: (date now | format date '%Y-%m-%d %H:%M:%S')"
|
||||
print $"DEBUG: Context: ($context | default 'no context')"
|
||||
print $"DEBUG: Error code: ($code)"
|
||||
}
|
||||
|
||||
if ($env.PROVISIONING_OUT | is-empty) {
|
||||
if $span == null and $context == null {
|
||||
error make --unspanned { msg: ( $error + "\n" + $msg + $suggestion) }
|
||||
} else if $span != null and $env.PROVISIONING_METADATA {
|
||||
error make {
|
||||
msg: $error
|
||||
label: {
|
||||
text: $"($msg) (_ansi blue)($context)(_ansi reset)($suggestion)"
|
||||
span: $span
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error make --unspanned {
|
||||
msg: ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_print ( $error + "\n" + $msg + "\n" + $"(_ansi blue)($context | default "" )(_ansi reset)($suggestion)")
|
||||
}
|
||||
}
|
||||
|
||||
export def safe-execute [
|
||||
command: closure
|
||||
context: string
|
||||
--fallback: closure
|
||||
]: any {
|
||||
try {
|
||||
do $command
|
||||
} catch {|err|
|
||||
print $"⚠️ Warning: Error in ($context): ($err.msg)"
|
||||
if ($fallback | is-not-empty) {
|
||||
print "🔄 Executing fallback..."
|
||||
do $fallback
|
||||
} else {
|
||||
print $"🛑 Execution failed in ($context)"
|
||||
print $" Error: ($err.msg)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export def try [
|
||||
settings_data: record
|
||||
defaults_data: record
|
||||
]: nothing -> nothing {
|
||||
$settings_data.servers | each { |server|
|
||||
_print ( $defaults_data.defaults | merge $server )
|
||||
}
|
||||
_print ($settings_data.servers | get hostname)
|
||||
_print ($settings_data.servers | get 0).tasks
|
||||
let zli_cfg = (open "resources/oci-reg/zli-cfg" | from json)
|
||||
if $zli_cfg.sops? != null {
|
||||
_print "Found"
|
||||
} else {
|
||||
_print "NOT Found"
|
||||
}
|
||||
let pos = 0
|
||||
_print ($settings_data.servers | get $pos )
|
||||
}
|
||||
113
core/nulib/lib_provisioning/utils/files.nu
Normal file
113
core/nulib/lib_provisioning/utils/files.nu
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use std
|
||||
use ../secrets/lib.nu decode_secret_file
|
||||
use ../secrets/lib.nu get_secret_provider
|
||||
|
||||
export def find_file [
|
||||
start_path: string
|
||||
match_path: string
|
||||
only_first: bool
|
||||
] {
|
||||
mut found_path = ""
|
||||
mut search_path = $start_path
|
||||
let home_root = ($env.HOME | path dirname)
|
||||
while $found_path == "" and $search_path != "/" and $search_path != $home_root {
|
||||
if $search_path == "" { break }
|
||||
let res = if $only_first {
|
||||
(^find $search_path -type f -name $match_path -print -quit | complete)
|
||||
} else {
|
||||
(^find $search_path -type f -name $match_path err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
|
||||
}
|
||||
if $res.exit_code == 0 { $found_path = ($res.stdout | str trim ) }
|
||||
$search_path = ($search_path | path dirname)
|
||||
}
|
||||
$found_path
|
||||
}
|
||||
export def copy_file [
|
||||
source: string
|
||||
target: string
|
||||
quiet: bool
|
||||
] {
|
||||
let provider = (get_secret_provider)
|
||||
if $provider == "" or ($env.PROVISIONING_USE_SOPS == "" and $env.PROVISIONING_USE_KMS == "") {
|
||||
let ops = if $quiet { "" } else { "-v" }
|
||||
cp $ops $source $target
|
||||
return
|
||||
}
|
||||
(decode_secret_file $source $target $quiet)
|
||||
}
|
||||
export def copy_prov_files [
|
||||
src_root: string
|
||||
src_path: string
|
||||
target: string
|
||||
no_replace: bool
|
||||
quiet: bool
|
||||
] {
|
||||
mut path_name = ""
|
||||
let start_path = if $src_path == "" or $src_path == "." { $src_root } else { ($src_root | path join $src_path) } | str replace "." $env.PWD
|
||||
let p = ($start_path | path type)
|
||||
if not ($start_path | path exists) { return }
|
||||
if ($start_path | path type) != "dir" {
|
||||
# if ($"($target)/($path_name)" | path exists ) and $no_replace { return }
|
||||
copy_file $start_path $target $quiet
|
||||
return
|
||||
}
|
||||
for item in (glob ($start_path | path join "*")) {
|
||||
$path_name = ($item | path basename)
|
||||
if ($item | path type) == "dir" {
|
||||
if not ($target | path join $path_name | path exists) { ^mkdir -p ($target | path join $path_name) }
|
||||
copy_prov_files ($item | path dirname) $path_name ($target | path join $path_name) $no_replace $quiet
|
||||
} else if ($item | path exists) {
|
||||
if ($target | path join $path_name| path exists ) and $no_replace { continue }
|
||||
if not ($target | path exists) { ^mkdir -p $target }
|
||||
copy_file $item ($target | path join $path_name) $quiet
|
||||
}
|
||||
}
|
||||
}
|
||||
export def select_file_list [
|
||||
root_path: string
|
||||
title: string
|
||||
is_for_task: bool
|
||||
recursive_cnt: int
|
||||
]: nothing -> string {
|
||||
if ($env | get -o PROVISIONING_OUT | default "" | is-not-empty) or $env.PROVISIONING_NO_TERMINAL { return ""}
|
||||
if not ($root_path | path dirname | path exists) { return {} }
|
||||
_print $"(_ansi purple_bold)($title)(_ansi reset) ($root_path) "
|
||||
if (glob $root_path | length) == 0 { return {} }
|
||||
let pick_list = (ls ($root_path | into glob) | default [])
|
||||
let msg_sel = if $is_for_task {
|
||||
"Select one file"
|
||||
} else {
|
||||
"To use a file select one"
|
||||
}
|
||||
if ($pick_list | length) == 0 { return "" }
|
||||
let selection = if ($pick_list | length) > 1 {
|
||||
let prompt = $"(_ansi default_dimmed)($msg_sel) \(use arrows and press [enter] or [esc] to cancel\):(_ansi reset)"
|
||||
let pos_select = ($pick_list | each {|it| $"($it.modified) -> ($it.name | path basename)"} |input list --index $prompt)
|
||||
if $pos_select == null { return null }
|
||||
let selection = ($pick_list | get -o $pos_select)
|
||||
if not $is_for_task {
|
||||
_print $"\nFor (_ansi green_bold)($selection.name)(_ansi reset) file use:"
|
||||
}
|
||||
$selection
|
||||
} else {
|
||||
let selection = ($pick_list | get -o 0)
|
||||
if not $is_for_task {
|
||||
_print $"\n(_ansi default_dimmed)For a file (_ansi reset)(_ansi green_bold)($selection.name)(_ansi reset) use:"
|
||||
}
|
||||
$selection
|
||||
}
|
||||
let file_selection = if $selection.type == "dir" {
|
||||
let cnt = if $recursive_cnt > 0 {
|
||||
# print $recursive_cnt
|
||||
if ($recursive_cnt - 1) == 0 { return $selection }
|
||||
$recursive_cnt - 1
|
||||
} else { $recursive_cnt }
|
||||
return (select_file_list $selection.name $title $is_for_task $cnt)
|
||||
} else {
|
||||
$selection
|
||||
}
|
||||
if not $is_for_task {
|
||||
show_clip_to $"($file_selection.name)" true
|
||||
}
|
||||
$file_selection
|
||||
}
|
||||
47
core/nulib/lib_provisioning/utils/format.nu
Normal file
47
core/nulib/lib_provisioning/utils/format.nu
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use std
|
||||
|
||||
export def datalist_to_format [
|
||||
out: string
|
||||
data: list
|
||||
] {
|
||||
# Not supported "toml" => ($data | flatten | to toml )
|
||||
match $out {
|
||||
"json" => ( $data | to json )
|
||||
"yaml" => ( $data | to yaml )
|
||||
"text" => ( $data | to text )
|
||||
"md" => ( $data | to md )
|
||||
"nuon" => ( $data | to nuon )
|
||||
"csv" => ( $data | to csv )
|
||||
_ => {
|
||||
$data |table -e
|
||||
# if $cols != null {
|
||||
# let str_cols = ($cols | str replace "ips" "")
|
||||
# $ips = if ($cols | str contains "ips") {
|
||||
# # _print (mw_servers_ips $curr_settings $args --prov $prov --serverpos $serverpos)
|
||||
# ($data | each {|srv| | ($srv.ip_addresses |
|
||||
# each {|it| { hostname: $srv.hostname, ip: $it.address, access: $it.access, family: $it.family }})} |
|
||||
# flatten
|
||||
# )
|
||||
# }
|
||||
# #if $str_cols != "" {
|
||||
# # ($data | select -o ($str_cols | split row ","))
|
||||
# #}
|
||||
# } else {
|
||||
# $data
|
||||
# }
|
||||
}
|
||||
}
|
||||
}
|
||||
export def money_conversion [
|
||||
src: string
|
||||
target: string
|
||||
amount: float
|
||||
] {
|
||||
let host = 'api.frankfurter.app';
|
||||
let url = $"https://($host)/latest?amount=($amount)&from=($src)&to=($target)"
|
||||
#let data = (http get $url --raw --allow-errors)
|
||||
let res = (^curl -sSL $url err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
|
||||
if $res.exit_code == 0 and ($res.stdout | is-not-empty) {
|
||||
($res.stdout| from json | get -o rates | get -o $target | default 0)
|
||||
} else { 0 }
|
||||
}
|
||||
178
core/nulib/lib_provisioning/utils/generate.nu
Normal file
178
core/nulib/lib_provisioning/utils/generate.nu
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
#!/usr/bin/env -S nu
|
||||
# Author: JesusPerezLorenzo
|
||||
# Release: 1.0.4
|
||||
# Date: 6-2-2024
|
||||
|
||||
#use ../lib_provisioning/utils/templates.nu on_template_path
|
||||
|
||||
export def github_latest_tag [
|
||||
url: string = ""
|
||||
use_dev_release: bool = false
|
||||
id_target: string = "releases/tag"
|
||||
]: nothing -> string {
|
||||
#let res = (http get $url -r )
|
||||
if ($url | is-empty) { return "" }
|
||||
let res = (^curl -s $url | complete)
|
||||
let html_content = if ($res.exit_code != 0) {
|
||||
print $"🛑 Error (_ansi red)($url)(_ansi reset):\n ($res.exit_code) ($res.stderr)"
|
||||
return ""
|
||||
} else { $res.stdout }
|
||||
# curl -s https://github.com/project-zot/zot/tags | grep "<h2 " | grep "releases/tag"
|
||||
let versions = ($html_content | parse --regex '<h2 (?<a>.*?)</a>' | get -o a | each {|it|
|
||||
($it | parse --regex ($"($id_target)" + '/(?<version>.*?)"') | get version | get -o 0 | default "")
|
||||
})
|
||||
let list = if $use_dev_release {
|
||||
$versions
|
||||
} else {
|
||||
($versions | where {|it|
|
||||
not ($it | str contains "-rc") and not ($it | str contains "-alpha")
|
||||
})
|
||||
}
|
||||
$list | sort -r | get -o 0 | default ""
|
||||
}
|
||||
|
||||
export def value_input_list [
|
||||
input_type: string
|
||||
options_list: list
|
||||
msg: string
|
||||
default_value: string
|
||||
]: nothing -> string {
|
||||
let selection_pos = ( $options_list
|
||||
| input list --index (
|
||||
$"(_ansi default_dimmed)Select(_ansi reset) (_ansi yellow_bold)($msg)(_ansi reset) " +
|
||||
$"\n(_ansi default_dimmed)\(use arrow keys and press [enter] or [escape] for default '(_ansi reset)" +
|
||||
$"($default_value)(_ansi default_dimmed)'\)(_ansi reset)"
|
||||
))
|
||||
if $selection_pos != null {
|
||||
($options_list | get -o $selection_pos | default $default_value)
|
||||
} else { $default_value }
|
||||
}
|
||||
|
||||
export def value_input [
|
||||
input_type: string
|
||||
numchar: int
|
||||
msg: string
|
||||
default_value: string
|
||||
not_empty: bool
|
||||
]: nothing -> string {
|
||||
while true {
|
||||
let value_input = if $numchar > 0 {
|
||||
print ($"(_ansi yellow_bold)($msg)(_ansi reset) " +
|
||||
$"(_ansi default_dimmed) type value (_ansi green_bold)($numchar) chars(_ansi reset) " +
|
||||
$"(_ansi default_dimmed) default '(_ansi reset)" +
|
||||
$"($default_value)(_ansi default_dimmed)'(_ansi reset)"
|
||||
)
|
||||
(input --numchar $numchar)
|
||||
} else {
|
||||
print ($"(_ansi yellow_bold)($msg)(_ansi reset) " +
|
||||
$"(_ansi default_dimmed)\(type value and press [enter] default '(_ansi reset)" +
|
||||
$"($default_value)(_ansi default_dimmed)'\)(_ansi reset)"
|
||||
)
|
||||
(input)
|
||||
}
|
||||
if $not_empty and ($value_input | is-empty) {
|
||||
if ($default_value | is-not-empty) { return $default_value }
|
||||
continue
|
||||
} else if ($value_input | is-empty) {
|
||||
return $default_value
|
||||
}
|
||||
let result = match $input_type {
|
||||
"number" => {
|
||||
if ($value_input | parse --regex '^[0-9]' | length) > 0 { $value_input } else { "" }
|
||||
},
|
||||
"ipv4-address" => {
|
||||
if ($value_input | parse --regex '^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$' | length) > 0 { $value_input } else { "" }
|
||||
},
|
||||
_ => $value_input,
|
||||
}
|
||||
if $value_input != $result { continue }
|
||||
return $value_input
|
||||
}
|
||||
return $default_value
|
||||
}
|
||||
|
||||
export def "generate_title" [
|
||||
title: string
|
||||
]: nothing -> nothing {
|
||||
_print $"\n(_ansi purple)($env.PROVISIONING_NAME)(_ansi reset) (_ansi default_dimmed)generate:(_ansi reset) (_ansi cyan)($title)(_ansi reset)"
|
||||
_print $"(_ansi default_dimmed)-------------------------------------------------------------(_ansi reset)\n"
|
||||
}
|
||||
|
||||
export def "generate_data_items" [
|
||||
defs_gen: list = []
|
||||
defs_values: list = []
|
||||
]: nothing -> record {
|
||||
mut data = {}
|
||||
for it in $defs_values {
|
||||
let input_type = ($it | get -o input_type | default "")
|
||||
let options_list = ($it | get -o options_list | default [])
|
||||
let numchar = ($it | get -o numchar | default 0)
|
||||
let msg = ($it | get -o msg | default "")
|
||||
let default_value = match $input_type {
|
||||
"list-record" | "list" => ($it | get -o default_value | default []),
|
||||
"record" => ($it | get -o default_value | default {}),
|
||||
_ => ($it | get -o default_value | default ""),
|
||||
}
|
||||
let var = ($it | get -o var | default "")
|
||||
let not_empty = ($it | get -o not_empty | default false)
|
||||
print $input_type
|
||||
let value = match $input_type {
|
||||
"record" => (generate_data_items $it),
|
||||
"list-record" => {
|
||||
let record_key = ($it | get -o record | default "")
|
||||
let record_value = ($defs_gen | get -o $record_key | default [])
|
||||
print ($record_value | table -e)
|
||||
# where {|it| ($it | get -o $record_key | is-not-empty)} | get -o 0 | get -o $record_key | default [])
|
||||
if ($record_value | is-empty) { continue }
|
||||
mut val = []
|
||||
while true {
|
||||
let selection_pos = ( [ $"Add ($msg)", $"No more ($var)" ]
|
||||
| input list --index (
|
||||
$"(_ansi default_dimmed)Select(_ansi reset) (_ansi yellow_bold)($msg)(_ansi reset) " +
|
||||
$"\n(_ansi default_dimmed)\(use arrow keys and press [enter] or [escape] to finish '(_ansi reset)"
|
||||
))
|
||||
if $selection_pos == null or $selection_pos == 1 { break }
|
||||
$val = ($val | append (generate_data_items $defs_gen $record_value))
|
||||
}
|
||||
$val
|
||||
},
|
||||
"list" => (value_input_list $input_type $options_list $msg $default_value),
|
||||
_ => (value_input $input_type $numchar $msg $default_value $not_empty),
|
||||
}
|
||||
$data = ($data | merge { $var: $value })
|
||||
}
|
||||
$data
|
||||
}
|
||||
|
||||
export def "generate_data_def" [
|
||||
root_path: string
|
||||
infra_name: string
|
||||
infra_path: string
|
||||
created: bool
|
||||
inputfile: string = ""
|
||||
]: nothing -> nothing {
|
||||
let data = (if ($inputfile | is-empty) {
|
||||
let defs_path = ($root_path | path join $env.PROVISIONING_GENERATE_DIRPATH | path join $env.PROVISIONING_GENERATE_DEFSFILE)
|
||||
if ( $defs_path | path exists) {
|
||||
let data_gen = (open $defs_path)
|
||||
let title = $"($data_gen| get -o title | default "")"
|
||||
generate_title $title
|
||||
let defs_values = ($data_gen | get -o defs_values | default [])
|
||||
(generate_data_items $data_gen $defs_values)
|
||||
} else {
|
||||
if $env.PROVISIONING_DEBUG { _print $"🛑 ($env.PROVISIONING_NAME) generate: Invalid path (_ansi red)($defs_path)(_ansi reset)" }
|
||||
}
|
||||
} else {
|
||||
(open $inputfile)
|
||||
} | merge {
|
||||
infra_name: $infra_name,
|
||||
infra_path: $infra_path,
|
||||
})
|
||||
let vars_filepath = $"/tmp/data_($infra_name)_($env.NOW).yaml"
|
||||
($data | to yaml | str replace "$name" $infra_name| save -f $vars_filepath)
|
||||
let remove_files = if $env.PROVISIONING_DEBUG { false } else { true }
|
||||
on_template_path $infra_path $vars_filepath $remove_files true
|
||||
if not $env.PROVISIONING_DEBUG {
|
||||
rm -f $vars_filepath
|
||||
}
|
||||
}
|
||||
23
core/nulib/lib_provisioning/utils/help.nu
Normal file
23
core/nulib/lib_provisioning/utils/help.nu
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export def parse_help_command [
|
||||
source: string
|
||||
name?: string
|
||||
--task: closure
|
||||
--ismod
|
||||
--end
|
||||
] {
|
||||
#use utils/interface.nu end_run
|
||||
let args = $env.PROVISIONING_ARGS? | default ""
|
||||
let has_help = if ($args | str contains "help") or ($args |str ends-with " h") {
|
||||
true
|
||||
} else if $name != null and $name == "help" or $name == "h" {
|
||||
true
|
||||
} else { false }
|
||||
if not $has_help { return }
|
||||
let mod_str = if $ismod { "-mod" } else { "" }
|
||||
^$env.PROVISIONING_NAME $mod_str ...($source | split row " ") --help
|
||||
if $task != null { do $task }
|
||||
if $end {
|
||||
if not $env.PROVISIONING_DEBUG { end_run "" }
|
||||
exit
|
||||
}
|
||||
}
|
||||
71
core/nulib/lib_provisioning/utils/imports.nu
Normal file
71
core/nulib/lib_provisioning/utils/imports.nu
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Import Helper Functions
|
||||
# Provides clean, environment-based imports to avoid relative paths
|
||||
|
||||
# Provider middleware imports
|
||||
export def prov-middleware []: nothing -> string {
|
||||
$env.PROVISIONING_PROV_LIB | path join "middleware.nu"
|
||||
}
|
||||
|
||||
export def prov-env-middleware []: nothing -> string {
|
||||
$env.PROVISIONING_PROV_LIB | path join "env_middleware.nu"
|
||||
}
|
||||
|
||||
# Provider-specific imports
|
||||
export def aws-env []: nothing -> string {
|
||||
$env.PROVISIONING_PROVIDERS_PATH | path join "aws" "nulib" "aws" "env.nu"
|
||||
}
|
||||
|
||||
export def aws-servers []: nothing -> string {
|
||||
$env.PROVISIONING_PROVIDERS_PATH | path join "aws" "nulib" "aws" "servers.nu"
|
||||
}
|
||||
|
||||
export def upcloud-env []: nothing -> string {
|
||||
$env.PROVISIONING_PROVIDERS_PATH | path join "upcloud" "nulib" "upcloud" "env.nu"
|
||||
}
|
||||
|
||||
export def upcloud-servers []: nothing -> string {
|
||||
$env.PROVISIONING_PROVIDERS_PATH | path join "upcloud" "nulib" "upcloud" "servers.nu"
|
||||
}
|
||||
|
||||
export def local-env []: nothing -> string {
|
||||
$env.PROVISIONING_PROVIDERS_PATH | path join "local" "nulib" "local" "env.nu"
|
||||
}
|
||||
|
||||
export def local-servers []: nothing -> string {
|
||||
$env.PROVISIONING_PROVIDERS_PATH | path join "local" "nulib" "local" "servers.nu"
|
||||
}
|
||||
|
||||
# Core module imports
|
||||
export def core-servers []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "servers"
|
||||
}
|
||||
|
||||
export def core-taskservs []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "taskservs"
|
||||
}
|
||||
|
||||
export def core-clusters []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "clusters"
|
||||
}
|
||||
|
||||
# Lib provisioning imports (for internal cross-references)
|
||||
export def lib-utils []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "lib_provisioning" "utils"
|
||||
}
|
||||
|
||||
export def lib-secrets []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "lib_provisioning" "secrets"
|
||||
}
|
||||
|
||||
export def lib-sops []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "lib_provisioning" "sops"
|
||||
}
|
||||
|
||||
export def lib-ai []: nothing -> string {
|
||||
$env.PROVISIONING_CORE_NULIB | path join "lib_provisioning" "ai"
|
||||
}
|
||||
|
||||
# Helper for dynamic imports with specific files
|
||||
export def import-path [base: string, file: string]: nothing -> string {
|
||||
$base | path join $file
|
||||
}
|
||||
50
core/nulib/lib_provisioning/utils/init.nu
Normal file
50
core/nulib/lib_provisioning/utils/init.nu
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
export def show_titles []: nothing -> nothing {
|
||||
if (detect_claude_code) { return false }
|
||||
if ($env.PROVISIONING_NO_TITLES? | default false) { return }
|
||||
if ($env.PROVISIONING_OUT | is-not-empty) { return }
|
||||
_print $"(_ansi blue_bold)(open -r ($env.PROVISIONING_RESOURCES | path join "ascii.txt"))(_ansi reset)"
|
||||
}
|
||||
export def use_titles [ ]: nothing -> bool {
|
||||
if ($env.PROVISIONING_NO_TITLES? | default false) { return }
|
||||
if ($env.PROVISIONING_NO_TERMINAL? | default false) { return false }
|
||||
if ($env.PROVISIONING_ARGS? | str contains "-h" ) { return false }
|
||||
if ($env.PROVISIONING_ARGS? | str contains "--notitles" ) { return false }
|
||||
if ($env.PROVISIONING_ARGS? | str contains "query") and ($env.PROVISIONING_ARGS? | str contains "-o" ) { return false }
|
||||
true
|
||||
}
|
||||
export def provisioning_init [
|
||||
helpinfo: bool
|
||||
module: string
|
||||
args: list<string> # Other options, use help to get info
|
||||
]: nothing -> nothing {
|
||||
if (use_titles) { show_titles }
|
||||
if $helpinfo != null and $helpinfo {
|
||||
let cmd_line: list<string> = if ($args| length) == 0 {
|
||||
$args | str join " "
|
||||
} else {
|
||||
$env.PROVISIONING_ARGS? | default ""
|
||||
}
|
||||
let cmd_args: list<string> = ($cmd_line | str replace "--helpinfo" "" |
|
||||
str replace "-h" "" | str replace $module "" | str trim | split row " "
|
||||
)
|
||||
if ($cmd_args | length) > 0 {
|
||||
# _print $"---($module)-- ($env.PROVISIONING_NAME) -mod '($module)' ($cmd_args) help"
|
||||
^$"($env.PROVISIONING_NAME)" "-mod" $"($module | str replace ' ' '|')" ...$cmd_args help
|
||||
# let str_mod_0 = ($cmd_args | get -o 0 | default "")
|
||||
# let str_mod_1 = ($cmd_args | get -o 1 | default "")
|
||||
# if $str_mod_1 != "" {
|
||||
# let final_args = ($cmd_args | drop nth 0 1)
|
||||
# _print $"---($module)-- ($env.PROVISIONING_NAME) -mod '($str_mod_0) ($str_mod_1)' ($cmd_args | drop nth 0) help"
|
||||
# ^$"($env.PROVISIONING_NAME)" "-mod" $"'($str_mod_0) ($str_mod_1)'" ...$final_args help
|
||||
# } else {
|
||||
# let final_args = ($cmd_args | drop nth 0)
|
||||
# _print $"---($module)-- ($env.PROVISIONING_NAME) -mod ($str_mod_0) ($cmd_args | drop nth 0) help"
|
||||
# ^$"($env.PROVISIONING_NAME)" "-mod" ($str_mod_0) ...$final_args help
|
||||
# }
|
||||
} else {
|
||||
^$"($env.PROVISIONING_NAME)" help
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
193
core/nulib/lib_provisioning/utils/interface.nu
Normal file
193
core/nulib/lib_provisioning/utils/interface.nu
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
export def _ansi [
|
||||
arg?: string
|
||||
--escape: record
|
||||
]: nothing -> string {
|
||||
if ($env | get -o PROVISIONING_NO_TERMINAL | default false) {
|
||||
""
|
||||
} else if (is-terminal --stdout) {
|
||||
if $escape != null {
|
||||
(ansi --escape $escape)
|
||||
} else {
|
||||
(ansi $arg)
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
export def format_out [
|
||||
data: string
|
||||
src?: string
|
||||
mode?: string
|
||||
]: nothing -> string {
|
||||
let msg = match $src {
|
||||
"json" => ($data | from json),
|
||||
_ => $data,
|
||||
}
|
||||
match $mode {
|
||||
"table" => {
|
||||
($msg | table -i false)
|
||||
},
|
||||
_ => { $msg }
|
||||
}
|
||||
}
|
||||
export def _print [
|
||||
data: string
|
||||
src?: string
|
||||
context?: string
|
||||
mode?: string
|
||||
-n # no newline
|
||||
]: nothing -> nothing {
|
||||
let output = ($env | get -o PROVISIONING_OUT| default "")
|
||||
if $n {
|
||||
if ($output | is-empty) {
|
||||
print -n $data
|
||||
}
|
||||
return
|
||||
}
|
||||
if ($output | is-empty) {
|
||||
print (format_out $data $src $mode)
|
||||
} else {
|
||||
match $output {
|
||||
"json" => {
|
||||
if $context != "result" { return }
|
||||
if $src == "json" {
|
||||
print ($data)
|
||||
} else {
|
||||
print ($data | to json)
|
||||
}
|
||||
},
|
||||
"yaml" | "yml" => {
|
||||
if $context != "result" { return }
|
||||
if $src == "json" {
|
||||
print ($data | from json | to yaml)
|
||||
} else {
|
||||
print ($data | to yaml)
|
||||
}
|
||||
},
|
||||
"toml" | "tml" => {
|
||||
if $context != "result" { return }
|
||||
if $src == "json" {
|
||||
print ($data | from json | to toml)
|
||||
} else {
|
||||
print ($data)
|
||||
}
|
||||
},
|
||||
"text" | "txt" => {
|
||||
if $context != "result" { return }
|
||||
print (format_out $data $src $mode)
|
||||
},
|
||||
_ => {
|
||||
if ($output | str ends-with ".json" ) {
|
||||
if $context != "result" { return }
|
||||
(if $src == "json" {
|
||||
($data)
|
||||
} else {
|
||||
($data | to json)
|
||||
} | save --force $output)
|
||||
} else if ($output | str ends-with ".yaml" ) {
|
||||
if $context != "result" { return }
|
||||
(if $src == "json" {
|
||||
($data | from json | to yaml)
|
||||
} else {
|
||||
($data | to yaml)
|
||||
} | save --force $output)
|
||||
} else if ($output | str ends-with ".toml" ) {
|
||||
if $context != "result" { return }
|
||||
(if $src == "json" {
|
||||
($data | from json | to toml)
|
||||
} else {
|
||||
($data)
|
||||
} | save --force $output)
|
||||
} else if ($output | str ends-with ".text" ) or ($output | str ends-with ".txt" ) {
|
||||
if $context != "result" { return }
|
||||
format_out $data $src $mode | save --force $output
|
||||
} else {
|
||||
format_out $data $src $mode | save --append $output
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export def end_run [
|
||||
context: string
|
||||
]: nothing -> nothing {
|
||||
if ($env.PROVISIONING_OUT | is-not-empty) { return }
|
||||
if ($env.PROVISIONING_NO_TITLES? | default false) { return false }
|
||||
if (detect_claude_code) { return false }
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
_print $"\n(_ansi blue)----🌥 ----🌥 ----🌥 ---- oOo ----🌥 ----🌥 ----🌥 ---- (_ansi reset)"
|
||||
} else {
|
||||
let the_context = if $context != "" { $" to ($context)" } else { "" }
|
||||
if (is-terminal --stdout) {
|
||||
_print $"\n(_ansi cyan)Thanks for using (_ansi blue_bold)($env.PROVISIONING_URL | ansi link --text 'Provisioning')(_ansi reset)"
|
||||
if $the_context != "" {
|
||||
_print $"(_ansi yellow_dimmed)($the_context)(_ansi reset)"
|
||||
}
|
||||
_print ($env.PROVISIONING_URL | ansi link --text $"(_ansi default_dimmed)Click here for more info or visit \n($env.PROVISIONING_URL)(_ansi reset)")
|
||||
} else {
|
||||
_print $"\n(_ansi cyan)Thanks for using (_ansi blue_bold) Provisioning [($env.PROVISIONING_URL)](_ansi reset)($the_context)"
|
||||
_print $"(_ansi default_dimmed)For more info or visit ($env.PROVISIONING_URL)(_ansi reset)"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export def show_clip_to [
|
||||
msg: string
|
||||
show: bool
|
||||
]: nothing -> nothing {
|
||||
if $show { _print $msg }
|
||||
if (is-terminal --stdout) {
|
||||
clip_copy $msg $show
|
||||
}
|
||||
}
|
||||
|
||||
export def log_debug [
|
||||
msg: string
|
||||
]: nothing -> nothing {
|
||||
use std
|
||||
std log debug $msg
|
||||
# std assert (1 == 1)
|
||||
}
|
||||
|
||||
#// Examples:
|
||||
#// desktop_run_notify "Port scan" "Done" { port scan 8.8.8.8 53 }
|
||||
#// desktop_run_notify "Task try" "Done" --timeout 5sec
|
||||
export def desktop_run_notify [
|
||||
title: string
|
||||
body: string
|
||||
task?: closure
|
||||
--timeout: duration
|
||||
--icon: string
|
||||
] {
|
||||
let icon_path = if $icon == null {
|
||||
$env.PROVISIONING_NOTIFY_ICON
|
||||
} else { $icon }
|
||||
let time_out = if $timeout == null {
|
||||
8sec
|
||||
} else { $timeout }
|
||||
if $task != null {
|
||||
let start = date now
|
||||
let result = do $task
|
||||
let end = date now
|
||||
let total = $end - $start | format duration sec
|
||||
let result_typ = ($result | describe)
|
||||
let msg = if $result_typ == "bool" {
|
||||
(if $result { "✅ done " } else { $"🛑 fail "})
|
||||
} else if ($result_typ | str starts-with "record") {
|
||||
(if $result.status { "✅ done " } else { $"🛑 fail ($result.error)" })
|
||||
} else { "" }
|
||||
let time_body = $"($body) ($msg) finished in ($total) "
|
||||
( notify_msg $title $body $icon_path $time_body $timeout $task )
|
||||
return $result
|
||||
} else {
|
||||
( notify_msg $title $body $icon_path "" $timeout $task )
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
export def detect_claude_code []: nothing -> bool {
|
||||
let claudecode = ($env.CLAUDECODE? | default "" | str contains "1")
|
||||
let entrypoint = ($env.CLAUDE_CODE_ENTRYPOINT? | default "" | str contains "cli")
|
||||
$claudecode or $entrypoint
|
||||
}
|
||||
70
core/nulib/lib_provisioning/utils/logging.nu
Normal file
70
core/nulib/lib_provisioning/utils/logging.nu
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Enhanced logging system for provisioning tool
|
||||
|
||||
export def log-info [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"ℹ️ ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
|
||||
export def log-success [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"✅ ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
|
||||
export def log-warning [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"⚠️ ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
|
||||
export def log-error [
|
||||
message: string
|
||||
context?: string
|
||||
details?: string
|
||||
] {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
let details_str = if ($details | is-not-empty) { $"\n Details: ($details)" } else { "" }
|
||||
print $"🛑 ($timestamp)($context_str) ($message)($details_str)"
|
||||
}
|
||||
|
||||
export def log-debug [
|
||||
message: string
|
||||
context?: string
|
||||
] {
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
let timestamp = (date now | format date '%Y-%m-%d %H:%M:%S')
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"🐛 ($timestamp)($context_str) ($message)"
|
||||
}
|
||||
}
|
||||
|
||||
export def log-step [
|
||||
step: string
|
||||
total_steps: int
|
||||
current_step: int
|
||||
context?: string
|
||||
] {
|
||||
let progress = $"($current_step)/($total_steps)"
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"🔄 ($progress)($context_str) ($step)"
|
||||
}
|
||||
|
||||
export def log-progress [
|
||||
message: string
|
||||
percent: int
|
||||
context?: string
|
||||
] {
|
||||
let context_str = if ($context | is-not-empty) { $" [($context)]" } else { "" }
|
||||
print $"📊 ($context_str) ($message) ($percent)%"
|
||||
}
|
||||
23
core/nulib/lib_provisioning/utils/mod.nu
Normal file
23
core/nulib/lib_provisioning/utils/mod.nu
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
# Exclude minor or specific parts for global 'export use'
|
||||
export use interface.nu *
|
||||
export use clean.nu *
|
||||
export use error.nu *
|
||||
export use help.nu *
|
||||
export use init.nu *
|
||||
|
||||
export use generate.nu *
|
||||
export use undefined.nu *
|
||||
|
||||
export use qr.nu *
|
||||
export use ssh.nu *
|
||||
|
||||
export use settings.nu *
|
||||
export use templates.nu *
|
||||
# export use test.nu
|
||||
|
||||
export use format.nu *
|
||||
export use files.nu *
|
||||
|
||||
export use on_select.nu *
|
||||
export use imports.nu *
|
||||
65
core/nulib/lib_provisioning/utils/on_select.nu
Normal file
65
core/nulib/lib_provisioning/utils/on_select.nu
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
export def run_on_selection [
|
||||
select: string
|
||||
name: string
|
||||
item_path: string
|
||||
main_path: string
|
||||
root_path: string
|
||||
]: nothing -> nothing {
|
||||
if not ($item_path | path exists) { return }
|
||||
match $select {
|
||||
"edit" | "editor" | "ed" | "e" => {
|
||||
let cmd = ($env | get -o EDITOR | default "vi")
|
||||
let full_cmd = $"($cmd) ($main_path)"
|
||||
^($cmd) $main_path
|
||||
show_clip_to $full_cmd true
|
||||
},
|
||||
"view" | "vw" | "v" => {
|
||||
let cmd = ($env| get -o PROVISIONING_FILEVIEWER | default (if (^bash -c "type -P bat" | is-not-empty) { "bat" } else { "cat" }))
|
||||
let full_cmd = $"($cmd) ($main_path)"
|
||||
^($cmd) $main_path
|
||||
show_clip_to $full_cmd true
|
||||
},
|
||||
"list" | "ls" | "l" => {
|
||||
let full_cmd = $"ls -l ($item_path)"
|
||||
print (ls $item_path | each {|it| {
|
||||
name: ($it.name | str replace $root_path ""),
|
||||
type: $it.type, size: $it.size, modified: $it.modified
|
||||
}})
|
||||
show_clip_to $full_cmd true
|
||||
},
|
||||
"tree" | "tr" | "t" => {
|
||||
let full_cmd = $"tree -L 3 ($item_path)"
|
||||
^tree -L 3 $item_path
|
||||
show_clip_to $full_cmd true
|
||||
},
|
||||
"code" | "c" => {
|
||||
let full_cmd = $"code ($item_path)"
|
||||
^code $item_path
|
||||
show_clip_to $full_cmd true
|
||||
},
|
||||
"shell" | "sh" | "s" => {
|
||||
let full_cmd = $"($env.SHELL) -c " + $"cd ($item_path) ; ($env.SHELL)"
|
||||
print $"(_ansi default_dimmed)Use [ctrl-d] or 'exit' to end with(_ansi reset) ($env.SHELL)"
|
||||
^($env.SHELL) -c $"cd ($item_path) ; ($env.SHELL)"
|
||||
show_titles
|
||||
_print "Command "
|
||||
(show_clip_to $full_cmd false)
|
||||
},
|
||||
"nu"| "n" => {
|
||||
let full_cmd = $"($env.NU) -i -e " + $"cd ($item_path)"
|
||||
_print $"(_ansi default_dimmed)Use [ctrl-d] or 'exit' to end with(_ansi reset) nushell\n"
|
||||
^($env.NU) -i -e $"cd ($item_path)"
|
||||
show_titles
|
||||
_print "Command "
|
||||
(show_clip_to $full_cmd false)
|
||||
},
|
||||
"" => {
|
||||
_print $"($name): ($item_path)"
|
||||
show_clip_to $item_path false
|
||||
},
|
||||
_ => {
|
||||
_print $"($select) ($name): ($item_path)"
|
||||
show_clip_to $item_path false
|
||||
}
|
||||
}
|
||||
}
|
||||
5
core/nulib/lib_provisioning/utils/qr.nu
Normal file
5
core/nulib/lib_provisioning/utils/qr.nu
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export def "make_qr" [
|
||||
url?: string
|
||||
] {
|
||||
show_qr ($url | default $env.PROVISIONING_URL)
|
||||
}
|
||||
501
core/nulib/lib_provisioning/utils/settings.nu
Normal file
501
core/nulib/lib_provisioning/utils/settings.nu
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
use ../../../../providers/prov_lib/middleware.nu *
|
||||
use ../context.nu *
|
||||
use ../sops/mod.nu *
|
||||
|
||||
export def find_get_settings [
|
||||
--infra (-i): string # Infra directory
|
||||
--settings (-s): string # Settings path
|
||||
include_notuse: bool = false
|
||||
no_error: bool = false
|
||||
]: nothing -> record {
|
||||
#use utils/settings.nu [ load_settings ]
|
||||
if $infra != null {
|
||||
if $settings != null {
|
||||
(load_settings --infra $infra --settings $settings $include_notuse $no_error)
|
||||
} else {
|
||||
(load_settings --infra $infra $include_notuse $no_error)
|
||||
}
|
||||
} else {
|
||||
if $settings != null {
|
||||
(load_settings --settings $settings $include_notuse $no_error)
|
||||
} else {
|
||||
(load_settings $include_notuse $no_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
export def check_env [
|
||||
]: nothing -> bool {
|
||||
# TuDO
|
||||
true
|
||||
}
|
||||
export def get_context_infra_path [
|
||||
]: nothing -> string {
|
||||
let context = (setup_user_context)
|
||||
if $context == null or $context.infra == null { return "" }
|
||||
if $context.infra_path? != null and ($context.infra_path | path join $context.infra | path exists) {
|
||||
return ($context.infra_path| path join $context.infra)
|
||||
}
|
||||
if ($env.PROVISIONING_INFRA_PATH | path join $context.infra | path exists) {
|
||||
return ($env.PROVISIONING_INFRA_PATH | path join $context.infra)
|
||||
}
|
||||
""
|
||||
}
|
||||
export def get_infra [
|
||||
infra?: string
|
||||
]: nothing -> string {
|
||||
if ($infra | is-not-empty) {
|
||||
if ($infra | path exists) {
|
||||
$infra
|
||||
} else if ($infra | path join $env.PROVISIONING_DFLT_SET | path exists) {
|
||||
$infra
|
||||
} else if ($env.PROVISIONING_INFRA_PATH | path join $infra | path join $env.PROVISIONING_DFLT_SET | path exists) {
|
||||
$env.PROVISIONING_INFRA_PATH | path join $infra
|
||||
} else {
|
||||
let text = $"($infra) on ($env.PROVISIONING_INFRA_PATH | path join $infra)"
|
||||
(throw-error "🛑 Path not found " $text "get_infra" --span (metadata $infra).span)
|
||||
}
|
||||
} else {
|
||||
if ($env.PWD | path join $env.PROVISIONING_DFLT_SET | path exists) {
|
||||
$env.PWD
|
||||
} else if ($env.PROVISIONING_INFRA_PATH | path join ($env.PWD | path basename) |
|
||||
path join $env.PROVISIONING_DFLT_SET | path exists) {
|
||||
$env.PROVISIONING_INFRA_PATH | path join ($env.PWD | path basename)
|
||||
} else {
|
||||
let context_path = get_context_infra_path
|
||||
if $context_path != "" { return $context_path }
|
||||
$env.PROVISIONING_KLOUD_PATH
|
||||
}
|
||||
}
|
||||
}
|
||||
export def parse_kcl_file [
|
||||
src: string
|
||||
target: string
|
||||
append: bool
|
||||
msg: string
|
||||
err_exit?: bool = false
|
||||
]: nothing -> bool {
|
||||
# Try nu_plugin_kcl first if available
|
||||
let format = if $env.PROVISIONING_WK_FORMAT == "json" { "json" } else { "yaml" }
|
||||
let result = (process_kcl_file $src $format)
|
||||
if ($result | is-empty) {
|
||||
let text = $"kcl ($src) failed code ($result.exit_code)"
|
||||
(throw-error $msg $text "parse_kcl_file" --span (metadata $result).span)
|
||||
if $err_exit { exit $result.exit_code }
|
||||
return false
|
||||
}
|
||||
if $append {
|
||||
$result | save --append $target
|
||||
} else {
|
||||
$result | save -f $target
|
||||
}
|
||||
true
|
||||
}
|
||||
export def load_from_wk_format [
|
||||
src: string
|
||||
]: nothing -> record {
|
||||
if not ( $src | path exists) { return {} }
|
||||
let data_raw = (open -r $src)
|
||||
if $env.PROVISIONING_WK_FORMAT == "json" {
|
||||
$data_raw | from json | default {}
|
||||
} else {
|
||||
$data_raw | from yaml | default {}
|
||||
}
|
||||
}
|
||||
export def load_defaults [
|
||||
src_path: string
|
||||
item_path: string
|
||||
target_path: string
|
||||
]: nothing -> string {
|
||||
if ($target_path | path exists) {
|
||||
if (is_sops_file $target_path) { decode_sops_file $src_path $target_path true }
|
||||
retrurn
|
||||
}
|
||||
let full_path = if ($item_path | path exists) {
|
||||
($item_path)
|
||||
} else if ($"($item_path).k" | path exists) {
|
||||
$"($item_path).k"
|
||||
} else if ($src_path | path dirname | path join $"($item_path).k" | path exists) {
|
||||
$src_path | path dirname | path join $"($item_path).k"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
if $full_path == "" { return true }
|
||||
if (is_sops_file $full_path) {
|
||||
decode_sops_file $full_path $target_path true
|
||||
(parse_kcl_file $target_path $target_path false $"🛑 load default settings failed ($target_path) ")
|
||||
} else {
|
||||
(parse_kcl_file $full_path $target_path false $"🛑 load default settings failed ($full_path)")
|
||||
}
|
||||
}
|
||||
export def get_provider_env [
|
||||
settings: record
|
||||
server: record
|
||||
]: nothing -> record {
|
||||
let prov_env_path = if ($server.prov_settings | path exists ) {
|
||||
$server.prov_settings
|
||||
} else {
|
||||
let file_path = ($settings.src_path | path join $server.prov_settings)
|
||||
if ($file_path | str ends-with '.k' ) { $file_path } else { $"($file_path).k" }
|
||||
}
|
||||
if not ($prov_env_path| path exists ) {
|
||||
if $env.PROVISIONING_DEBUG { _print $"🛑 load (_ansi cyan_bold)provider_env(_ansi reset) from ($server.prov_settings) failed at ($prov_env_path)" }
|
||||
return {}
|
||||
}
|
||||
let str_created_taskservs_dirpath = ($settings.data.created_taskservs_dirpath | default "/tmp" |
|
||||
str replace "\~" $env.HOME | str replace "NOW" $env.NOW | str replace "./" $"($settings.src_path)/")
|
||||
let created_taskservs_dirpath = if ($str_created_taskservs_dirpath | str starts-with "/" ) { $str_created_taskservs_dirpath } else { $settings.src_path | path join $str_created_taskservs_dirpath }
|
||||
if not ( $created_taskservs_dirpath | path exists) { ^mkdir -p $created_taskservs_dirpath }
|
||||
let source_settings_path = ($created_taskservs_dirpath | path join $"($prov_env_path | path basename)")
|
||||
let target_settings_path = ($created_taskservs_dirpath| path join $"($prov_env_path | path basename | str replace '.k' '').($env.PROVISIONING_WK_FORMAT)")
|
||||
let res = if (is_sops_file $prov_env_path) {
|
||||
decode_sops_file $prov_env_path $source_settings_path true
|
||||
(parse_kcl_file $source_settings_path $target_settings_path false $"🛑 load prov settings failed ($target_settings_path)")
|
||||
} else {
|
||||
cp $prov_env_path $source_settings_path
|
||||
(parse_kcl_file $source_settings_path $target_settings_path false $"🛑 load prov settings failed ($prov_env_path)")
|
||||
}
|
||||
if not $env.PROVISIONING_DEBUG { rm -f $source_settings_path }
|
||||
if $res and ($target_settings_path | path exists) {
|
||||
let data = (open $target_settings_path)
|
||||
if not $env.PROVISIONING_DEBUG { rm -f $target_settings_path }
|
||||
$data
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
}
|
||||
export def get_file_format [
|
||||
filename: string
|
||||
]: nothing -> string {
|
||||
if ($filename | str ends-with ".json") {
|
||||
"json"
|
||||
} else if ($filename | str ends-with ".yaml") {
|
||||
"yaml"
|
||||
} else {
|
||||
$env.PROVISIONING_WK_FORMAT
|
||||
}
|
||||
}
|
||||
export def save_provider_env [
|
||||
data: record
|
||||
settings: record
|
||||
provider_path: string
|
||||
]: nothing -> nothing {
|
||||
if ($provider_path | is-empty) or not ($provider_path | path dirname |path exists) {
|
||||
_print $"❗ Can not save provider env for (_ansi blue)($provider_path | path dirname)(_ansi reset) in (_ansi red)($provider_path)(_ansi reset )"
|
||||
return
|
||||
}
|
||||
if (get_file_format $provider_path) == "json" {
|
||||
$"data: ($data | to json | encode base64)" | save --force $provider_path
|
||||
} else {
|
||||
$"data: ($data | to yaml | encode base64)" | save --force $provider_path
|
||||
}
|
||||
let result = (on_sops "encrypt" $provider_path --quiet)
|
||||
if ($result | is-not-empty) {
|
||||
($result | save --force $provider_path)
|
||||
}
|
||||
}
|
||||
export def get_provider_data_path [
|
||||
settings: record
|
||||
server: record
|
||||
]: nothing -> string {
|
||||
let data_path = if ($settings.data.prov_data_dirpath | str starts-with "." ) {
|
||||
($settings.src_path | path join $settings.data.prov_data_dirpath)
|
||||
} else {
|
||||
$settings.data.prov_data_dirpath
|
||||
}
|
||||
if not ($data_path | path exists) { ^mkdir -p $data_path }
|
||||
($data_path | path join $"($server.provider)_cache.($env.PROVISIONING_WK_FORMAT)")
|
||||
}
|
||||
export def load_provider_env [
|
||||
settings: record
|
||||
server: record
|
||||
provider_path: string = ""
|
||||
]: nothing -> record {
|
||||
let data = if ($provider_path | is-not-empty) and ($provider_path |path exists) {
|
||||
let file_data = if (is_sops_file $provider_path) {
|
||||
on_sops "decrypt" $provider_path --quiet
|
||||
let result = (on_sops "decrypt" $provider_path --quiet)
|
||||
# --character-set binhex
|
||||
if (get_file_format $provider_path) == "json" {
|
||||
($result | from json | get -o data | default "" | decode base64 | decode | from json)
|
||||
} else {
|
||||
($result | from yaml | get -o data | default "" | decode base64 | decode | from yaml)
|
||||
}
|
||||
} else {
|
||||
open $provider_path
|
||||
}
|
||||
if ($file_data | is-empty) or ($file_data | get -o main | get -o vpc) == "?" {
|
||||
# (throw-error $"load provider ($server.provider) settings failed" $"($provider_path) no main data"
|
||||
# "load_provider_env" --span (metadata $data).span)
|
||||
if $env.PROVISIONING_DEBUG { _print $"load provider ($server.provider) settings failed ($provider_path) no main data in load_provider_env" }
|
||||
{}
|
||||
} else {
|
||||
$file_data
|
||||
}
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
if ($data | is-empty) {
|
||||
let new_data = (get_provider_env $settings $server)
|
||||
if ($new_data | is-not-empty) and ($provider_path | is-not-empty) { save_provider_env $new_data $settings $provider_path }
|
||||
$new_data
|
||||
} else {
|
||||
$data
|
||||
}
|
||||
}
|
||||
export def load_provider_settings [
|
||||
settings: record
|
||||
server: record
|
||||
]: nothing -> record {
|
||||
let data_path = if ($settings.data.prov_data_dirpath | str starts-with "." ) {
|
||||
($settings.src_path | path join $settings.data.prov_data_dirpath)
|
||||
} else { $settings.data.prov_data_dirpath }
|
||||
if ($data_path | is-empty) {
|
||||
(throw-error $"load provider ($server.provider) settings failed" $"($settings.data.prov_data_dirpath)"
|
||||
"load_provider_settings" --span (metadata $data_path).span)
|
||||
}
|
||||
if not ($data_path | path exists) { ^mkdir -p $data_path }
|
||||
let provider_path = ($data_path | path join $"($server.provider)_cache.($env.PROVISIONING_WK_FORMAT)")
|
||||
let data = (load_provider_env $settings $server $provider_path)
|
||||
if ($data | is-empty) or ($data | get -o main | get -o vpc) == "?" {
|
||||
mw_create_cache $settings $server false
|
||||
(load_provider_env $settings $server $provider_path)
|
||||
} else {
|
||||
$data
|
||||
}
|
||||
}
|
||||
export def load [
|
||||
infra?: string
|
||||
in_src?: string
|
||||
include_notuse?: bool = false
|
||||
--no_error
|
||||
]: nothing -> record {
|
||||
let source = if $in_src == null or ($in_src | str ends-with '.k' ) { $in_src } else { $"($in_src).k" }
|
||||
let source_path = if $source != null and ($source | path type) == "dir" { $"($source)/($env.PROVISIONING_DFLT_SET)" } else { $source }
|
||||
let src_path = if $source_path != null and ($source_path | path exists) {
|
||||
$"./($source_path)"
|
||||
} else if $source_path != null and ($source_path | str ends-with $env.PROVISIONING_DFLT_SET) == false {
|
||||
if $no_error {
|
||||
return {}
|
||||
} else {
|
||||
(throw-error "🛑 invalid settings infra / path " $"file ($source) settings in ($infra)" "settings->load" --span (metadata $source).span)
|
||||
}
|
||||
} else if ($infra | is-empty) and ($env.PROVISIONING_DFLT_SET| is-not-empty ) and ($env.PROVISIONING_DFLT_SET | path exists) {
|
||||
$"./($env.PROVISIONING_DFLT_SET)"
|
||||
} else if ($infra | path join $env.PROVISIONING_DFLT_SET | path exists) {
|
||||
$infra | path join $env.PROVISIONING_DFLT_SET
|
||||
} else {
|
||||
if $no_error {
|
||||
return {}
|
||||
} else {
|
||||
(throw-error "🛑 invalid settings infra / path " $"file ($source) settings in ($infra)" "settings->load" --span (metadata $source_path).span)
|
||||
}
|
||||
}
|
||||
let src_dir = ($src_path | path dirname)
|
||||
let infra_path = if $src_dir == "." {
|
||||
$env.PWD
|
||||
} else if ($src_dir | is-empty) {
|
||||
$env.PWD | path join $infra
|
||||
} else if ($src_dir | path exists ) and ( $src_dir | str starts-with "/") {
|
||||
$src_dir
|
||||
} else {
|
||||
$env.PWD | path join $src_dir
|
||||
}
|
||||
let wk_settings_path = mktemp -d
|
||||
if not (parse_kcl_file $"($src_path)" $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)" false "🛑 load settings failed ") { return }
|
||||
if $env.PROVISIONING_DEBUG { _print $"DEBUG source path: ($src_path)" }
|
||||
let settings_data = open $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
|
||||
if $env.PROVISIONING_DEBUG { _print $"DEBUG work path: ($wk_settings_path)" }
|
||||
let servers_paths = ($settings_data | get -o servers_paths | default [])
|
||||
# Set full path for provider data
|
||||
let data_fullpath = if ($settings_data.prov_data_dirpath | str starts-with "." ) {
|
||||
($src_dir | path join $settings_data.prov_data_dirpath)
|
||||
} else { $settings_data.prov_data_dirpath }
|
||||
mut list_servers = []
|
||||
mut providers_settings = []
|
||||
for it in $servers_paths {
|
||||
let file_path = if ($it | str ends-with ".k") {
|
||||
$it
|
||||
} else {
|
||||
$"($it).k"
|
||||
}
|
||||
let server_path = if ($file_path | str starts-with "/") {
|
||||
$file_path
|
||||
} else {
|
||||
($src_path | path dirname | path join $file_path)
|
||||
}
|
||||
if not ($server_path | path exists) {
|
||||
if $no_error {
|
||||
"" | save $server_path
|
||||
} else {
|
||||
(throw-error "🛑 server path not found " ($server_path) "load each on list_servers" --span (metadata $servers_paths).span)
|
||||
}
|
||||
}
|
||||
let target_settings_path = $"($wk_settings_path)/($it | str replace --all "/" "_").($env.PROVISIONING_WK_FORMAT)"
|
||||
if not (parse_kcl_file ($server_path | path join $server_path) $target_settings_path false "🛑 load settings failed ") { return }
|
||||
#if not (parse_kcl_file $server_path $target_settings_path false "🛑 load settings failed ") { return }
|
||||
if not ( $target_settings_path | path exists) { continue }
|
||||
let servers_defs = (open $target_settings_path | default {})
|
||||
for srvr in ($servers_defs | get -o servers | default []) {
|
||||
if not $include_notuse and $srvr.not_use { continue }
|
||||
let provider = $srvr.provider
|
||||
if not ($"($wk_settings_path)/($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)" | path exists ) {
|
||||
let dflt_item = ($settings_data.defaults_provs_dirpath | path join $"($provider)($settings_data.defaults_provs_suffix)")
|
||||
let dflt_item_fullpath = if ($dflt_item | str starts-with "." ) {
|
||||
($src_dir | path join $dflt_item)
|
||||
} else { $dflt_item }
|
||||
load_defaults $src_path $dflt_item_fullpath ($wk_settings_path | path join $"($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)")
|
||||
}
|
||||
# Loading defaults provider ...
|
||||
let server_with_dflts = if ($"($wk_settings_path)/($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)" | path exists ) {
|
||||
open ($"($wk_settings_path)/($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)") | merge $srvr
|
||||
} else { $srvr }
|
||||
# Loading provider data settings
|
||||
let server_prov_data = if ($data_fullpath | path join $"($provider)($settings_data.prov_data_suffix)" | path exists) {
|
||||
(load_defaults $src_dir ($data_fullpath | path join $"($provider)($settings_data.prov_data_suffix)")
|
||||
($wk_settings_path | path join $"($provider)($settings_data.prov_data_suffix)")
|
||||
)
|
||||
if (($wk_settings_path | path join $"($provider)($settings_data.prov_data_suffix)") | path exists) {
|
||||
$server_with_dflts | merge (load_from_wk_format ($wk_settings_path | path join $"($provider)($settings_data.prov_data_suffix)"))
|
||||
} else { $server_with_dflts }
|
||||
} else { $server_with_dflts }
|
||||
# Loading provider data settings
|
||||
let server_with_data = if ($data_fullpath | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)" | path exists) {
|
||||
(load_defaults $src_dir ($data_fullpath | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)")
|
||||
($wk_settings_path | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)")
|
||||
)
|
||||
if ($wk_settings_path | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)" | path exists) {
|
||||
$server_prov_data | merge (load_from_wk_format ($wk_settings_path | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)"))
|
||||
} else { $server_prov_data }
|
||||
} else { $server_prov_data }
|
||||
$list_servers = ($list_servers | append $server_with_data)
|
||||
if ($providers_settings | where {|it| $it.provider == $provider} | length) == 0 {
|
||||
$providers_settings = ($providers_settings | append {
|
||||
provider: $provider,
|
||||
settings: (load_provider_settings {
|
||||
data: $settings_data,
|
||||
providers: $providers_settings,
|
||||
src: ($src_path | path basename),
|
||||
src_path: ($src_path | path dirname),
|
||||
infra: ($infra_path | path basename),
|
||||
infra_path: ($infra_path |path dirname),
|
||||
wk_path: $wk_settings_path
|
||||
}
|
||||
$server_with_data)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
#{ settings: $settings_data, servers: ($list_servers | flatten) }
|
||||
# | to ($env.PROVISIONING_WK_FORMAT) | save --append $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
|
||||
# let servers_settings = { servers: ($list_servers | flatten) }
|
||||
let servers_settings = { servers: $list_servers }
|
||||
if $env.PROVISIONING_WK_FORMAT == "json" {
|
||||
#$servers_settings | to json | save --append $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
|
||||
$servers_settings | to json | save --force $"($wk_settings_path)/servers.($env.PROVISIONING_WK_FORMAT)"
|
||||
} else {
|
||||
#$servers_settings | to yaml | save --append $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
|
||||
$servers_settings | to yaml | save --force $"($wk_settings_path)/servers.($env.PROVISIONING_WK_FORMAT)"
|
||||
}
|
||||
#let $settings_data = (open $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)")
|
||||
let $settings_data = ($settings_data | merge $servers_settings )
|
||||
{
|
||||
data: $settings_data,
|
||||
providers: $providers_settings,
|
||||
src: ($src_path | path basename),
|
||||
src_path: ($src_path | path dirname),
|
||||
infra: ($infra_path | path basename),
|
||||
infra_path: ($infra_path |path dirname),
|
||||
wk_path: $wk_settings_path
|
||||
}
|
||||
}
|
||||
export def load_settings [
|
||||
--infra (-i): string
|
||||
--settings (-s): string # Settings path
|
||||
include_notuse: bool = false
|
||||
no_error: bool = false
|
||||
]: nothing -> record {
|
||||
let kld = get_infra (if $infra == null { "" } else { $infra })
|
||||
if $no_error {
|
||||
(load $kld $settings $include_notuse --no_error)
|
||||
} else {
|
||||
(load $kld $settings $include_notuse)
|
||||
}
|
||||
# let settings = (load $kld $settings $exclude_not_use)
|
||||
# if $env.PROVISIONING_USE_SOPS? != "" {
|
||||
# use sops/lib.nu check_sops
|
||||
# check_sops $settings.src_path
|
||||
# }
|
||||
# $settings
|
||||
}
|
||||
export def save_settings_file [
|
||||
settings: record
|
||||
target_file: string
|
||||
match_text: string
|
||||
new_text: string
|
||||
mark_changes: bool = false
|
||||
]: nothing -> nothing {
|
||||
let it_path = if ($target_file | path exists) {
|
||||
$target_file
|
||||
} else if ($settings.src_path | path join $"($target_file).k" | path exists) {
|
||||
($settings.src_path | path join $"($target_file).k")
|
||||
} else if ($settings.src_path | path join $"($target_file).($env.PROVISIONING_WK_FORMAT)" | path exists) {
|
||||
($settings.src_path | path join $"($target_file).($env.PROVISIONING_WK_FORMAT)")
|
||||
} else {
|
||||
_print $"($target_file) not found in ($settings.src_path)"
|
||||
return false
|
||||
}
|
||||
if (is_sops_file $it_path) {
|
||||
let result = (on_sops "decrypt" $it_path --quiet)
|
||||
if ($result | is-empty) {
|
||||
(throw-error $"🛑 saving settings to ($it_path)"
|
||||
$"from ($match_text) to ($new_text)"
|
||||
$"in ($target_file)" --span (metadata $it_path).span)
|
||||
return false
|
||||
} else {
|
||||
$result | str replace $match_text $new_text| save --force $it_path
|
||||
let en_result = (on_sops "encrypt" $it_path --quiet)
|
||||
if ($en_result | is-not-empty) {
|
||||
($en_result | save --force $it_path)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
open $it_path --raw | str replace $match_text $new_text | save --force $it_path
|
||||
}
|
||||
#if $it_path != "" and (^grep -q $match_text $it_path | complete).exit_code == 0 {
|
||||
# if (^sed -i $"s/($match_text)/($match_text)\"($new_text)\"/g" $it_path | complete).exit_code == 0 {
|
||||
_print $"($target_file) saved with new value "
|
||||
if $mark_changes {
|
||||
if ($settings.wk_path | path join "changes" | path exists) == false {
|
||||
$"($it_path) has been changed" | save ($settings.wk_path | path join "changes") --append
|
||||
}
|
||||
} else if ($env.PROVISIONING_MODULE | is-not-empty) {
|
||||
^($env.PROVISIONING_NAME) "-mod" $env.PROVISIONING_MODULE $env.PROVISIONING_ARGS
|
||||
exit
|
||||
}
|
||||
# }
|
||||
#}
|
||||
}
|
||||
export def save_servers_settings [
|
||||
settings: record
|
||||
match_text: string
|
||||
new_text: string
|
||||
]: nothing -> nothing {
|
||||
$settings.data.servers_paths | each { | it |
|
||||
save_settings_file $settings $it $match_text $new_text
|
||||
}
|
||||
}
|
||||
export def settings_with_env [
|
||||
settings: record
|
||||
] {
|
||||
mut $servers_with_ips = []
|
||||
for srv in ($settings.data.servers) {
|
||||
let pub_ip = (mw_ip_from_cache $settings $srv false)
|
||||
if ($pub_ip | is-empty) {
|
||||
$servers_with_ips = ($servers_with_ips | append ($srv))
|
||||
} else {
|
||||
$servers_with_ips = ($servers_with_ips | append ($srv | merge { network_public_ip: $pub_ip }))
|
||||
}
|
||||
}
|
||||
($settings | merge { data: ($settings.data | merge { servers: $servers_with_ips}) })
|
||||
}
|
||||
54
core/nulib/lib_provisioning/utils/simple_validation.nu
Normal file
54
core/nulib/lib_provisioning/utils/simple_validation.nu
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Simple validation functions for provisioning tool
|
||||
|
||||
export def check-required [
|
||||
value: any
|
||||
name: string
|
||||
]: bool {
|
||||
if ($value | is-empty) {
|
||||
print $"🛑 Required parameter '($name)' is missing or empty"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def check-path [
|
||||
path: string
|
||||
]: bool {
|
||||
if ($path | is-empty) {
|
||||
print "🛑 Path parameter is empty"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def check-path-exists [
|
||||
path: string
|
||||
]: bool {
|
||||
if not ($path | path exists) {
|
||||
print $"🛑 Path '($path)' does not exist"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def check-command [
|
||||
command: string
|
||||
]: bool {
|
||||
let result = (^bash -c $"type -P ($command)" | complete)
|
||||
if $result.exit_code != 0 {
|
||||
print $"🛑 Command '($command)' not found in PATH"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def safe-run [
|
||||
command: closure
|
||||
context: string
|
||||
]: any {
|
||||
try {
|
||||
do $command
|
||||
} catch {|err|
|
||||
print $"⚠️ Warning: Error in ($context): ($err.msg)"
|
||||
}
|
||||
}
|
||||
141
core/nulib/lib_provisioning/utils/ssh.nu
Normal file
141
core/nulib/lib_provisioning/utils/ssh.nu
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
|
||||
export def ssh_cmd [
|
||||
settings: record
|
||||
server: record
|
||||
with_bash: bool
|
||||
cmd: string
|
||||
live_ip: string
|
||||
] {
|
||||
let ip = if $live_ip != "" {
|
||||
$live_ip
|
||||
} else {
|
||||
#use ../../../../providers/prov_lib/middleware.nu mw_get_ip
|
||||
(mw_get_ip $settings $server $server.liveness_ip false)
|
||||
}
|
||||
if $ip == "" { return false }
|
||||
if not (check_connection $server $ip "ssh_cmd") { return false }
|
||||
let remote_cmd = if $with_bash {
|
||||
let ops = if $env.PROVISIONING_DEBUG { "-x" } else { "" }
|
||||
$"bash ($ops) ($cmd)"
|
||||
} else { $cmd }
|
||||
let ssh_loglevel = if $env.PROVISIONING_DEBUG {
|
||||
_print $"Run ($remote_cmd) in ($server.installer_user)@($ip)"
|
||||
"-o LogLevel=info"
|
||||
} else {
|
||||
"-o LogLevel=quiet"
|
||||
}
|
||||
let res = (^ssh "-o" ($env.SSH_OPS | get -o 0) "-o" ($env.SSH_OPS | get -o 1) "-o" IdentitiesOnly=yes $ssh_loglevel
|
||||
"-i" ($server.ssh_key_path | str replace ".pub" "")
|
||||
$"($server.installer_user)@($ip)" ($remote_cmd) | complete)
|
||||
if $res.exit_code != 0 {
|
||||
_print $"❗ run ($remote_cmd) in ($server.hostname) errors ($res.stdout ) "
|
||||
return false
|
||||
}
|
||||
if $env.PROVISIONING_DEBUG and $remote_cmd != "ls" { _print $res.stdout }
|
||||
true
|
||||
}
|
||||
export def scp_to [
|
||||
settings: record
|
||||
server: record
|
||||
source: list<string>
|
||||
target: string
|
||||
live_ip: string
|
||||
] {
|
||||
let ip = if $live_ip != "" {
|
||||
$live_ip
|
||||
} else {
|
||||
#use ../../../../providers/prov_lib/middleware.nu mw_get_ip
|
||||
(mw_get_ip $settings $server $server.liveness_ip false)
|
||||
}
|
||||
if $ip == "" { return false }
|
||||
if not (check_connection $server $ip "scp_to") { return false }
|
||||
let source_files = ($source | str join " ")
|
||||
let ssh_loglevel = if $env.PROVISIONING_DEBUG {
|
||||
_print $"Sending ($source | str join ' ') to ($server.installer_user)@($ip)/tmp/($target)"
|
||||
_print $"scp -o ($env.SSH_OPS | get -o 0) -o ($env.SSH_OPS | get -o 1) -o IdentitiesOnly=yes -i ($server.ssh_key_path | str replace ".pub" "") ($source_files) ($server.installer_user)@($ip):($target)"
|
||||
"-o LogLevel=info"
|
||||
} else {
|
||||
"-o LogLevel=quiet"
|
||||
}
|
||||
let res = (^scp "-o" ($env.SSH_OPS | get -o 0) "-o" ($env.SSH_OPS | get -o 1) "-o" IdentitiesOnly=yes $ssh_loglevel
|
||||
"-i" ($server.ssh_key_path | str replace ".pub" "")
|
||||
$source_files $"($server.installer_user)@($ip):($target)" | complete)
|
||||
if $res.exit_code != 0 {
|
||||
_print $"❗ copy ($target | str join ' ') to ($server.hostname) errors ($res.stdout ) "
|
||||
return false
|
||||
}
|
||||
if $env.PROVISIONING_DEBUG { _print $res.stdout }
|
||||
true
|
||||
}
|
||||
export def scp_from [
|
||||
settings: record
|
||||
server: record
|
||||
source: string
|
||||
target: string
|
||||
live_ip: string
|
||||
] {
|
||||
let ip = if $live_ip != "" {
|
||||
$live_ip
|
||||
} else {
|
||||
#use ../../../../providers/prov_lib/middleware.nu mw_get_ip
|
||||
(mw_get_ip $settings $server $server.liveness_ip false)
|
||||
}
|
||||
if $ip == "" { return false }
|
||||
if not (check_connection $server $ip "scp_from") { return false }
|
||||
let ssh_loglevel = if $env.PROVISIONING_DEBUG {
|
||||
_print $"Getting ($target | str join ' ') from ($server.installer_user)@($ip)/tmp/($target)"
|
||||
"-o LogLevel=info"
|
||||
} else {
|
||||
"-o LogLevel=quiet"
|
||||
}
|
||||
let res = (^scp "-o" ($env.SSH_OPS | get -o 0) "-o" ($env.SSH_OPS | get -o 1) "-o" IdentitiesOnly=yes $ssh_loglevel
|
||||
"-i" ($server.ssh_key_path | str replace ".pub" "")
|
||||
$"($server.installer_user)@($ip):($source)" $target | complete)
|
||||
if $res.exit_code != 0 {
|
||||
_print $"❗ copy ($source) from ($server.hostname) to ($target) errors ($res.stdout ) "
|
||||
return false
|
||||
}
|
||||
if $env.PROVISIONING_DEBUG { _print $res.stdout }
|
||||
true
|
||||
}
|
||||
export def ssh_cp_run [
|
||||
settings: record
|
||||
server: record
|
||||
source: list<string>
|
||||
target: string
|
||||
with_bash: bool
|
||||
live_ip: string
|
||||
ssh_remove: bool
|
||||
] {
|
||||
let ip = if $live_ip != "" {
|
||||
$live_ip
|
||||
} else {
|
||||
#use ../../../../providers/prov_lib/middleware.nu mw_get_ip
|
||||
(mw_get_ip $settings $server $server.liveness_ip false)
|
||||
}
|
||||
if $ip == "" {
|
||||
_print $"❗ ssh_cp_run (_ansi red_bold)No IP(_ansi reset) to (_ansi green_bold)($server.hostname)(_ansi reset)"
|
||||
return false
|
||||
}
|
||||
if not (scp_to $settings $server $source $target $ip) { return false }
|
||||
if not (ssh_cmd $settings $server $with_bash $target $ip) { return false }
|
||||
if $env.PROVISIONING_SSH_DEBUG? != null and $env.PROVISIONING_SSH_DEBUG { return true }
|
||||
if $ssh_remove {
|
||||
return (ssh_cmd $settings $server false $"rm -f ($target)" $ip)
|
||||
}
|
||||
true
|
||||
}
|
||||
export def check_connection [
|
||||
server: record
|
||||
ip: string
|
||||
origin: string
|
||||
] {
|
||||
if not (port_scan $ip $server.liveness_port 1) {
|
||||
_print (
|
||||
$"\n🛑 (_ansi red)Error connection(_ansi reset) ($origin) (_ansi blue)($server.hostname)(_ansi reset) " +
|
||||
$"(_ansi blue_bold)($ip)(_ansi reset) at ($server.liveness_port) (_ansi red_bold)failed(_ansi reset) "
|
||||
)
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
168
core/nulib/lib_provisioning/utils/templates.nu
Normal file
168
core/nulib/lib_provisioning/utils/templates.nu
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
export def run_from_template [
|
||||
template_path: string # Template path
|
||||
vars_path: string # Variable file with settings for template
|
||||
run_file: string # File to run
|
||||
out_file?: string # Out file path
|
||||
--check_mode # Use check mode to review and not create server
|
||||
--only_make # not run
|
||||
] {
|
||||
# Check if nu_plugin_tera is available
|
||||
if not $env.PROVISIONING_USE_TERA_PLUGIN {
|
||||
_print $"🛑 (_ansi red)Error(_ansi reset) nu_plugin_tera not available - template rendering not supported"
|
||||
return false
|
||||
}
|
||||
if not ( $template_path | path exists ) {
|
||||
_print $"🛑 (_ansi red)Error(_ansi reset) template ($template_path) (_ansi red)not found(_ansi reset)"
|
||||
return false
|
||||
}
|
||||
if not ( $vars_path | path exists ) {
|
||||
_print $"🛑 (_ansi red)Error(_ansi reset) vars file ($vars_path) (_ansi red)not found(_ansi reset)"
|
||||
return false
|
||||
}
|
||||
let out_file_name = ($out_file | default "")
|
||||
|
||||
# Debug: Show what file we're trying to open
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
_print $"🔍 Template vars file: ($vars_path)"
|
||||
if ($vars_path | path exists) {
|
||||
_print "📄 File preview (first 3 lines):"
|
||||
_print (open $vars_path --raw | lines | take 3 | str join "\n")
|
||||
} else {
|
||||
_print $"❌ File does not exist!"
|
||||
}
|
||||
}
|
||||
|
||||
# Load variables from YAML/JSON file
|
||||
let vars = if ($vars_path | path exists) {
|
||||
if $env.PROVISIONING_DEBUG {
|
||||
_print $"🔍 Parsing YAML configuration: ($vars_path)"
|
||||
}
|
||||
|
||||
# Check for common YAML syntax issues before attempting to parse
|
||||
let content = (open $vars_path --raw)
|
||||
let unquoted_vars = ($content | lines | enumerate | where {|line| $line.item =~ '\s+\w+:\s+\$\w+'})
|
||||
|
||||
if ($unquoted_vars | length) > 0 {
|
||||
_print ""
|
||||
_print $"🛑 (_ansi red_bold)INFRASTRUCTURE CONFIGURATION ERROR(_ansi reset)"
|
||||
_print $"📄 Failed to parse YAML variables file: (_ansi yellow)($vars_path | path basename)(_ansi reset)"
|
||||
_print ""
|
||||
_print $"(_ansi blue_bold)Diagnosis:(_ansi reset)"
|
||||
_print "• Found unquoted variable references (invalid YAML syntax):"
|
||||
for $var in $unquoted_vars {
|
||||
let line_num = ($var.index + 1)
|
||||
let line_content = ($var.item | str trim)
|
||||
_print $" Line ($line_num): (_ansi red)($line_content)(_ansi reset)"
|
||||
}
|
||||
_print ""
|
||||
_print $"(_ansi blue_bold)Root Cause:(_ansi reset)"
|
||||
_print $"KCL-to-YAML conversion is not properly handling string variables."
|
||||
|
||||
# Extract variable names from the problematic lines
|
||||
let sample_vars = ($unquoted_vars | take 3 | each {|line|
|
||||
($line.item | str trim | split row " " | last)
|
||||
} | str join ", ")
|
||||
|
||||
if ($sample_vars | is-not-empty) {
|
||||
_print $"Example variables: ($sample_vars) should be quoted or resolved."
|
||||
} else {
|
||||
_print "String variables should be quoted or resolved during conversion."
|
||||
}
|
||||
_print ""
|
||||
_print $"(_ansi blue_bold)Fix Required:(_ansi reset)"
|
||||
_print $"1. Check KCL configuration generation process"
|
||||
_print $"2. Ensure variables are properly quoted or resolved during YAML generation"
|
||||
_print $"3. Source KCL files appear correct, issue is in conversion step"
|
||||
_print ""
|
||||
_print $"(_ansi blue_bold)Infrastructure file:(_ansi reset) ($vars_path)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# If no obvious issues found, attempt to parse YAML
|
||||
open $vars_path
|
||||
} else {
|
||||
_print $"❌ Variables file not found: ($vars_path)"
|
||||
return false
|
||||
}
|
||||
|
||||
# Use nu_plugin_tera for template rendering
|
||||
let result = (render_template $template_path $vars)
|
||||
# let result = if $result.exit_code == 0 {
|
||||
# {exit_code: 0, stdout: $result.stdout, stderr: ""}
|
||||
# } else {
|
||||
# {exit_code: 1, stdout: "", stderr: $"Template rendering failed for ($template_path)"}
|
||||
# }
|
||||
#if $result.exit_code != 0 {
|
||||
|
||||
if ($result | is-empty) {
|
||||
let text = $"(_ansi yellow)template(_ansi reset): ($template_path)\n(_ansi yellow)vars(_ansi reset): ($vars_path)\n(_ansi red)Failed(_ansi reset)"
|
||||
print $result
|
||||
print $"(_ansi red)ERROR(_ansi red) nu_plugin_tera render:\n($text)"
|
||||
exit
|
||||
}
|
||||
if not $only_make and $env.PROVISIONING_DEBUG or ($check_mode and ($out_file_name | is-empty)) {
|
||||
if $env.PROVISIONING_DEBUG and not $check_mode {
|
||||
_print $"Result running: \n (_ansi default_dimmed)nu_plugin_tera render ($template_path) ($vars_path)(_ansi reset)"
|
||||
# _print $"\n(_ansi yellow_bold)exit code: ($result.exit_code)(_ansi reset)"
|
||||
}
|
||||
let cmd = ($env| get -o PROVISIONING_FILEVIEWER | default (if (^bash -c "type -P bat" | is-not-empty) { "bat" } else { "cat" }))
|
||||
if $cmd != "bat" { _print $"(_ansi magenta_bold)----------------------------------------------------------------------------------------------------------------(_ansi reset)"}
|
||||
(echo $result | run-external $cmd -)
|
||||
if $cmd != "bat" { _print $"(_ansi magenta_bold)----------------------------------------------------------------------------------------------------------------(_ansi reset)"}
|
||||
_print $"Saved in (_ansi green_bold)($run_file)(_ansi reset)"
|
||||
}
|
||||
$result | str replace --all "\\ " "\\" | save --append $run_file
|
||||
if $only_make {
|
||||
if ($out_file_name | is-not-empty) {
|
||||
(cat $run_file | tee { save -f $out_file_name } | ignore)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if $check_mode and not $only_make {
|
||||
if $out_file_name == "" {
|
||||
_print $"✅ No errors found !\nTo save command to a file, run next time adding: (_ansi blue)--outfile \(-o\)(_ansi reset) file-path-to-save "
|
||||
} else {
|
||||
(cat $run_file | tee { save -f $out_file_name } | ignore)
|
||||
_print $"✅ No errors found !\nSave in (_ansi green_bold)(_ansi i)($out_file_name)(_ansi reset)"
|
||||
}
|
||||
return true
|
||||
}
|
||||
if $out_file_name != "" and ($out_file_name | path type) == "file" {
|
||||
(^bash $run_file | save --force $out_file_name)
|
||||
} else {
|
||||
let res = if $env.PROVISIONING_DEBUG {
|
||||
(^bash -x $run_file | complete)
|
||||
} else {
|
||||
(^bash $run_file | complete)
|
||||
}
|
||||
if $res.exit_code != 0 {
|
||||
_print $"\n🛑 (_ansi red)Error(_ansi reset) run from template ($template_path | path basename) (_ansi green_bold)($run_file)(_ansi reset) (_ansi red_bold)failed(_ansi reset) "
|
||||
_print $"\n($res.stdout)"
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def on_template_path [
|
||||
source_path: string
|
||||
vars_path: string
|
||||
remove_path: bool
|
||||
on_error_exit: bool
|
||||
] {
|
||||
for it in (^ls ...(glob $"($source_path)/*")| lines) {
|
||||
let item = ($it | str trim | str replace -r ':$' '')
|
||||
if ($item | is-empty) or ($item | path basename | str starts-with "tmp.") or ($item | path basename | str starts-with "_") { continue }
|
||||
if ($item | path type) == "dir" {
|
||||
if (ls $item | length) == 0 { continue }
|
||||
(on_template_path $item $vars_path $remove_path $on_error_exit)
|
||||
continue
|
||||
}
|
||||
if not ($item | str ends-with ".j2") or not ($item | path exists) { continue }
|
||||
if not (run_from_template $item $vars_path ($item | str replace ".j2" "") --only_make) {
|
||||
echo $"🛑 Error on_template_path (_ansi red_bold)($item)(_ansi reset) and vars (_ansi yellow_bold)($vars_path)(_ansi reset)"
|
||||
if $on_error_exit { exit 1 }
|
||||
}
|
||||
if $remove_path { rm -f $item }
|
||||
}
|
||||
}
|
||||
9
core/nulib/lib_provisioning/utils/test.nu
Normal file
9
core/nulib/lib_provisioning/utils/test.nu
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
export def on_test [] {
|
||||
use nupm/
|
||||
|
||||
cd $"($env.PROVISIONING)/core/nulib"
|
||||
nupm test test_addition
|
||||
cd $env.PWD
|
||||
nupm test basecamp_addition
|
||||
}
|
||||
11
core/nulib/lib_provisioning/utils/ui.nu
Normal file
11
core/nulib/lib_provisioning/utils/ui.nu
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
# Exclude minor or specific parts for global 'export use'
|
||||
|
||||
|
||||
export use clean.nu *
|
||||
export use error.nu *
|
||||
export use help.nu *
|
||||
|
||||
export use interface.nu *
|
||||
export use undefined.nu *
|
||||
|
||||
25
core/nulib/lib_provisioning/utils/undefined.nu
Normal file
25
core/nulib/lib_provisioning/utils/undefined.nu
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
export def option_undefined [
|
||||
root: string
|
||||
src: string
|
||||
info?: string
|
||||
] {
|
||||
_print $"🛑 invalid_option ($src) ($info)"
|
||||
_print $"\nUse (_ansi blue_bold)($env.PROVISIONING_NAME) ($root) ($src) help(_ansi reset) for help on commands and options"
|
||||
}
|
||||
|
||||
export def invalid_task [
|
||||
src: string
|
||||
task: string
|
||||
--end
|
||||
] {
|
||||
let show_src = {|color|
|
||||
if $src == "" { "" } else { $" (_ansi $color)($src)(_ansi reset)"}
|
||||
}
|
||||
if $task != "" {
|
||||
_print $"🛑 invalid (_ansi blue)($env.PROVISIONING_NAME)(_ansi reset)(do $show_src "yellow") task or option: (_ansi red)($task)(_ansi reset)"
|
||||
} else {
|
||||
_print $"(_ansi blue)($env.PROVISIONING_NAME)(_ansi reset)(do $show_src "yellow") no task or option found !"
|
||||
}
|
||||
_print $"Use (_ansi blue_bold)($env.PROVISIONING_NAME)(_ansi reset)(do $show_src "blue_bold") (_ansi blue_bold)help(_ansi reset) for help on commands and options"
|
||||
if $end and not $env.PROVISIONING_DEBUG { end_run "" }
|
||||
}
|
||||
93
core/nulib/lib_provisioning/utils/validation.nu
Normal file
93
core/nulib/lib_provisioning/utils/validation.nu
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Enhanced validation utilities for provisioning tool
|
||||
|
||||
export def validate-required [
|
||||
value: any
|
||||
name: string
|
||||
context?: string
|
||||
]: bool {
|
||||
if ($value | is-empty) {
|
||||
print $"🛑 Required parameter '($name)' is missing or empty"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
print $"💡 Please provide a value for '($name)'"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-path [
|
||||
path: string
|
||||
context?: string
|
||||
--must-exist
|
||||
]: bool {
|
||||
if ($path | is-empty) {
|
||||
print "🛑 Path parameter is empty"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if $must_exist and not ($path | path exists) {
|
||||
print $"🛑 Path '($path)' does not exist"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
print "💡 Check if the path exists and you have proper permissions"
|
||||
return false
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-command [
|
||||
command: string
|
||||
context?: string
|
||||
]: bool {
|
||||
let cmd_exists = (^bash -c $"type -P ($command)" | complete)
|
||||
if $cmd_exists.exit_code != 0 {
|
||||
print $"🛑 Command '($command)' not found in PATH"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
print $"💡 Install '($command)' or add it to your PATH"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def safe-execute [
|
||||
command: closure
|
||||
context: string
|
||||
--fallback: closure
|
||||
]: any {
|
||||
try {
|
||||
do $command
|
||||
} catch {|err|
|
||||
print $"⚠️ Warning: Error in ($context): ($err.msg)"
|
||||
if $fallback != null {
|
||||
print "🔄 Executing fallback..."
|
||||
do $fallback
|
||||
} else {
|
||||
print $"🛑 Execution failed in ($context)"
|
||||
print $"Error: ($err.msg)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export def validate-settings [
|
||||
settings: record
|
||||
required_fields: list
|
||||
]: bool {
|
||||
let missing_fields = ($required_fields | where {|field|
|
||||
($settings | get -o $field | is-empty)
|
||||
})
|
||||
|
||||
if ($missing_fields | length) > 0 {
|
||||
print "🛑 Missing required settings fields:"
|
||||
$missing_fields | each {|field| print $" - ($field)"}
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
121
core/nulib/lib_provisioning/utils/validation_helpers.nu
Normal file
121
core/nulib/lib_provisioning/utils/validation_helpers.nu
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Validation helper functions for provisioning tool
|
||||
|
||||
export def validate-required [
|
||||
value: any
|
||||
name: string
|
||||
context?: string
|
||||
]: bool {
|
||||
if ($value | is-empty) {
|
||||
print $"🛑 Required parameter '($name)' is missing or empty"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
print $"💡 Please provide a value for '($name)'"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-path [
|
||||
path: string
|
||||
context?: string
|
||||
--must-exist
|
||||
]: bool {
|
||||
if ($path | is-empty) {
|
||||
print "🛑 Path parameter is empty"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if $must_exist and not ($path | path exists) {
|
||||
print $"🛑 Path '($path)' does not exist"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
print "💡 Check if the path exists and you have proper permissions"
|
||||
return false
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-command [
|
||||
command: string
|
||||
context?: string
|
||||
]: bool {
|
||||
let cmd_exists = (^bash -c $"type -P ($command)" | complete)
|
||||
if $cmd_exists.exit_code != 0 {
|
||||
print $"🛑 Command '($command)' not found in PATH"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
print $"💡 Install '($command)' or add it to your PATH"
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-ip [
|
||||
ip: string
|
||||
context?: string
|
||||
]: bool {
|
||||
let ip_parts = ($ip | split row ".")
|
||||
if ($ip_parts | length) != 4 {
|
||||
print $"🛑 Invalid IP address format: ($ip)"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let valid_parts = ($ip_parts | each {|part|
|
||||
let num = ($part | into int)
|
||||
$num >= 0 and $num <= 255
|
||||
})
|
||||
|
||||
if not ($valid_parts | all {|valid| $valid}) {
|
||||
print $"🛑 Invalid IP address values: ($ip)"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-port [
|
||||
port: int
|
||||
context?: string
|
||||
]: bool {
|
||||
if $port < 1 or $port > 65535 {
|
||||
print $"🛑 Invalid port number: ($port). Must be between 1 and 65535"
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
export def validate-settings [
|
||||
settings: record
|
||||
required_fields: list
|
||||
context?: string
|
||||
]: bool {
|
||||
let missing_fields = ($required_fields | where {|field|
|
||||
($settings | get -o $field | is-empty)
|
||||
})
|
||||
|
||||
if ($missing_fields | length) > 0 {
|
||||
print "🛑 Missing required settings fields:"
|
||||
$missing_fields | each {|field| print $" - ($field)"}
|
||||
if ($context | is-not-empty) {
|
||||
print $"Context: ($context)"
|
||||
}
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
285
core/nulib/lib_provisioning/utils/version_core.nu
Normal file
285
core/nulib/lib_provisioning/utils/version_core.nu
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#!/usr/bin/env nu
|
||||
# Agnostic Version Management Core
|
||||
# No hardcoded tools or specific implementations
|
||||
|
||||
# use ../utils/error.nu *
|
||||
# use ../utils/format.nu *
|
||||
|
||||
# Generic version record schema
|
||||
export def version-schema []: nothing -> record {
|
||||
{
|
||||
id: "" # Unique identifier
|
||||
type: "" # Component type (tool/provider/taskserv/cluster)
|
||||
version: "" # Current version
|
||||
fixed: false # Version pinning
|
||||
source: {} # Source configuration
|
||||
detector: {} # Detection configuration
|
||||
updater: {} # Update configuration
|
||||
metadata: {} # Any additional data
|
||||
}
|
||||
}
|
||||
|
||||
# Generic version operations interface
|
||||
export def version-operations []: nothing -> record {
|
||||
{
|
||||
detect: { |config| "" } # Detect installed version
|
||||
fetch: { |config| "" } # Fetch available versions
|
||||
compare: { |v1, v2| 0 } # Compare versions
|
||||
update: { |config, version| {} } # Update to version
|
||||
}
|
||||
}
|
||||
|
||||
# Version comparison (works with semantic and non-semantic versions)
|
||||
export def compare-versions [
|
||||
v1: string
|
||||
v2: string
|
||||
--strategy: string = "semantic" # semantic, string, numeric, custom
|
||||
]: nothing -> int {
|
||||
if $v1 == $v2 { return 0 }
|
||||
if ($v1 | is-empty) { return (-1) }
|
||||
if ($v2 | is-empty) { return 1 }
|
||||
|
||||
match $strategy {
|
||||
"semantic" => {
|
||||
# Try semantic versioning
|
||||
let parts1 = ($v1 | split row "." | each { |p|
|
||||
($p | str trim | into int) | default 0
|
||||
})
|
||||
let parts2 = ($v2 | split row "." | each { |p|
|
||||
($p | str trim | into int) | default 0
|
||||
})
|
||||
|
||||
let max_len = ([$parts1 $parts2] | each { |it| $it | length } | math max)
|
||||
|
||||
for i in 0..<$max_len {
|
||||
let p1 = ($parts1 | get -o $i | default 0)
|
||||
let p2 = ($parts2 | get -o $i | default 0)
|
||||
|
||||
if $p1 < $p2 { return (-1) }
|
||||
if $p1 > $p2 { return 1 }
|
||||
}
|
||||
0
|
||||
}
|
||||
"string" => {
|
||||
# Simple string comparison
|
||||
if $v1 < $v2 { (-1) } else if $v1 > $v2 { 1 } else { 0 }
|
||||
}
|
||||
"numeric" => {
|
||||
# Numeric comparison (for build numbers)
|
||||
let n1 = ($v1 | into float | default 0)
|
||||
let n2 = ($v2 | into float | default 0)
|
||||
if $n1 < $n2 { (-1) } else if $n1 > $n2 { 1 } else { 0 }
|
||||
}
|
||||
_ => 0
|
||||
}
|
||||
}
|
||||
|
||||
# Execute command and extract version
|
||||
export def detect-version [
|
||||
config: record # Detection configuration
|
||||
]: nothing -> string {
|
||||
if ($config | is-empty) { return "" }
|
||||
|
||||
let method = ($config | get -o method | default "command")
|
||||
|
||||
match $method {
|
||||
"command" => {
|
||||
let cmd = ($config | get -o command | default "")
|
||||
if ($cmd | is-empty) { return "" }
|
||||
|
||||
let result = (^sh -c $cmd err> /dev/null | complete)
|
||||
if $result.exit_code == 0 {
|
||||
let output = $result.stdout
|
||||
# Apply extraction pattern if provided
|
||||
if ($config | get -o pattern | is-not-empty) {
|
||||
let parsed = ($output | parse -r $config.pattern)
|
||||
if ($parsed | length) > 0 {
|
||||
let row = ($parsed | get 0)
|
||||
let capture_name = ($config | get -o capture | default "capture0")
|
||||
($row | get -o $capture_name | default "")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
$output | str trim
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
"file" => {
|
||||
let path = ($config | get -o path | default "")
|
||||
if not ($path | path exists) { return "" }
|
||||
|
||||
let content = (open $path)
|
||||
if ($config | get -o field | is-not-empty) {
|
||||
$content | get -o $config.field | default ""
|
||||
} else {
|
||||
$content | str trim
|
||||
}
|
||||
}
|
||||
"api" => {
|
||||
let url = ($config | get -o url | default "")
|
||||
if ($url | is-empty) { return "" }
|
||||
|
||||
let result = (http get $url --headers [User-Agent "nushell-version-checker"] | complete)
|
||||
if $result.exit_code == 0 and ($result.stdout | length) > 0 {
|
||||
let response = ($result.stdout | from json)
|
||||
if ($config | get -o field | is-not-empty) {
|
||||
$response | get -o $config.field | default ""
|
||||
} else {
|
||||
$response | to text | str trim
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
"script" => {
|
||||
# Execute custom script
|
||||
let script = ($config | get -o script | default "")
|
||||
if ($script | is-empty) { return "" }
|
||||
|
||||
(nu -c $script | str trim | default "")
|
||||
}
|
||||
_ => ""
|
||||
}
|
||||
}
|
||||
|
||||
# Fetch available versions from source
|
||||
export def fetch-versions [
|
||||
config: record # Source configuration
|
||||
--limit: int = 10
|
||||
]: nothing -> list {
|
||||
if ($config | is-empty) { return [] }
|
||||
|
||||
let type = ($config | get -o type | default "")
|
||||
|
||||
match $type {
|
||||
"github" => {
|
||||
let repo = ($config | get -o repo | default "")
|
||||
if ($repo | is-empty) { return [] }
|
||||
|
||||
# Try releases first, then tags
|
||||
let endpoints = [
|
||||
$"https://api.github.com/repos/($repo)/releases"
|
||||
$"https://api.github.com/repos/($repo)/tags"
|
||||
]
|
||||
|
||||
for endpoint in $endpoints {
|
||||
let response = (http get $endpoint --headers [User-Agent "nushell-version-checker"] | default [] | to json | from json | default [])
|
||||
if ($response | length) > 0 {
|
||||
return ($response
|
||||
| first $limit
|
||||
| each { |item|
|
||||
let version = ($item | get -o tag_name | default ($item | get -o name | default ""))
|
||||
$version | str replace -r '^v' ''
|
||||
})
|
||||
}
|
||||
}
|
||||
[]
|
||||
}
|
||||
"docker" => {
|
||||
let image = ($config | get -o image | default "")
|
||||
if ($image | is-empty) { return [] }
|
||||
|
||||
# Parse namespace/repo
|
||||
let parts = ($image | split row "/")
|
||||
let namespace = if ($parts | length) > 1 { $parts | get 0 } else { "library" }
|
||||
let repo = ($parts | last)
|
||||
|
||||
let url = $"https://hub.docker.com/v2/namespaces/($namespace)/repositories/($repo)/tags"
|
||||
let result = (http get $url --headers [User-Agent "nushell-version-checker"] | complete)
|
||||
if $result.exit_code == 0 and ($result.stdout | length) > 0 {
|
||||
let response = ($result.stdout | from json)
|
||||
if ($response | get -o results | is-not-empty) {
|
||||
$response
|
||||
| get -o results
|
||||
| first $limit
|
||||
| each { |tag| $tag.name }
|
||||
| where { |v| $v !~ "latest|dev|nightly|edge|alpha|beta|rc" }
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
}
|
||||
"url" => {
|
||||
let url = ($config | get -o url | default "")
|
||||
if ($url | is-empty) { return [] }
|
||||
|
||||
let result = (http get $url --headers [User-Agent "nushell-version-checker"] | complete)
|
||||
if $result.exit_code == 0 and ($result.stdout | length) > 0 {
|
||||
let response = ($result.stdout | from json)
|
||||
let field = ($config | get -o field | default "")
|
||||
if ($field | is-not-empty) {
|
||||
$response | get -o $field | default []
|
||||
} else {
|
||||
[$response | to text | str trim]
|
||||
}
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
}
|
||||
"script" => {
|
||||
let script = ($config | get -o script | default "")
|
||||
if ($script | is-empty) { return [] }
|
||||
|
||||
(nu -c $script | lines | default [])
|
||||
}
|
||||
_ => []
|
||||
}
|
||||
}
|
||||
|
||||
# Generic version check
|
||||
export def check-version [
|
||||
component: record
|
||||
--fetch-latest = false
|
||||
--respect-fixed = true
|
||||
]: nothing -> record {
|
||||
# Detect installed version
|
||||
let installed = if ($component | get -o detector | is-not-empty) {
|
||||
(detect-version $component.detector)
|
||||
} else { "" }
|
||||
|
||||
# Get configured version
|
||||
let configured = ($component | get -o version | default "")
|
||||
|
||||
# Check if fixed
|
||||
let is_fixed = ($component | get -o fixed | default false)
|
||||
|
||||
# Fetch latest if requested
|
||||
let latest = if $fetch_latest and (not $is_fixed or not $respect_fixed) {
|
||||
if ($component | get -o source | is-not-empty) {
|
||||
let versions = (fetch-versions $component.source --limit=1)
|
||||
if ($versions | length) > 0 { $versions | get 0 } else { $configured }
|
||||
} else { $configured }
|
||||
} else { $configured }
|
||||
|
||||
# Compare versions
|
||||
let comparison_strategy = ($component | get -o comparison | default "semantic")
|
||||
|
||||
let status = if $is_fixed and $respect_fixed {
|
||||
"fixed"
|
||||
} else if ($installed | is-empty) {
|
||||
"not_installed"
|
||||
} else if ($installed | is-not-empty) and ($latest != $installed) and ((compare-versions $installed $latest --strategy=$comparison_strategy) < 0) {
|
||||
"update_available"
|
||||
} else if (compare-versions $installed $configured --strategy=$comparison_strategy) < 0 {
|
||||
"behind_config"
|
||||
} else if (compare-versions $installed $configured --strategy=$comparison_strategy) > 0 {
|
||||
"ahead_config"
|
||||
} else {
|
||||
"up_to_date"
|
||||
}
|
||||
|
||||
{
|
||||
id: $component.id
|
||||
type: $component.type
|
||||
installed: $installed
|
||||
configured: $configured
|
||||
latest: $latest
|
||||
fixed: $is_fixed
|
||||
status: $status
|
||||
}
|
||||
}
|
||||
94
core/nulib/lib_provisioning/utils/version_formatter.nu
Normal file
94
core/nulib/lib_provisioning/utils/version_formatter.nu
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env nu
|
||||
# Configurable formatters for version status display
|
||||
|
||||
# Status icon mapping (configurable)
|
||||
export def status-icons []: nothing -> record {
|
||||
{
|
||||
fixed: "🔒"
|
||||
not_installed: "❌"
|
||||
update_available: "⬆️"
|
||||
behind_config: "⚠️"
|
||||
ahead_config: "🔄"
|
||||
up_to_date: "✅"
|
||||
unknown: "❓"
|
||||
}
|
||||
}
|
||||
|
||||
# Format status with configurable icons
|
||||
export def format-status [
|
||||
status: string
|
||||
--icons: record = {}
|
||||
]: nothing -> string {
|
||||
let icon_map = if ($icons | is-empty) { (status-icons) } else { $icons }
|
||||
let icon = ($icon_map | get -o $status | default $icon_map.unknown)
|
||||
|
||||
let text = match $status {
|
||||
"fixed" => "Fixed"
|
||||
"not_installed" => "Not installed"
|
||||
"update_available" => "Update available"
|
||||
"behind_config" => "Behind config"
|
||||
"ahead_config" => "Ahead of config"
|
||||
"up_to_date" => "Up to date"
|
||||
_ => "Unknown"
|
||||
}
|
||||
|
||||
$"($icon) ($text)"
|
||||
}
|
||||
|
||||
# Format version results as table
|
||||
export def format-results [
|
||||
results: list
|
||||
--group-by: string = "type"
|
||||
--show-fields: list = ["id", "installed", "configured", "latest", "status"]
|
||||
--icons: record = {}
|
||||
]: nothing -> nothing {
|
||||
if ($results | is-empty) {
|
||||
print "No components found"
|
||||
return
|
||||
}
|
||||
|
||||
# Group results if requested
|
||||
if ($group_by | is-not-empty) {
|
||||
let grouped = ($results | group-by { |r| $r | get -o $group_by | default "unknown" })
|
||||
|
||||
for group in ($grouped | transpose key value) {
|
||||
print $"\n### ($group.key | str capitalize)"
|
||||
|
||||
let formatted = ($group.value | each { |item|
|
||||
mut row = {}
|
||||
for field in $show_fields {
|
||||
if $field == "status" {
|
||||
$row = ($row | insert $field (format-status $item.status --icons=$icons))
|
||||
} else {
|
||||
$row = ($row | insert $field ($item | get -o $field | default ""))
|
||||
}
|
||||
}
|
||||
$row
|
||||
})
|
||||
|
||||
print ($formatted | table)
|
||||
}
|
||||
} else {
|
||||
# Direct table output
|
||||
let formatted = ($results | each { |item|
|
||||
mut row = {}
|
||||
for field in $show_fields {
|
||||
if $field == "status" {
|
||||
$row = ($row | insert $field (format-status $item.status --icons=$icons))
|
||||
} else {
|
||||
$row = ($row | insert $field ($item | get -o $field | default ""))
|
||||
}
|
||||
}
|
||||
$row
|
||||
})
|
||||
|
||||
print ($formatted | table)
|
||||
}
|
||||
|
||||
# Summary
|
||||
print "\n📊 Summary:"
|
||||
let by_status = ($results | group-by status)
|
||||
for status in ($by_status | transpose key value) {
|
||||
print $" (format-status $status.key --icons=$icons): ($status.value | length)"
|
||||
}
|
||||
}
|
||||
264
core/nulib/lib_provisioning/utils/version_loader.nu
Normal file
264
core/nulib/lib_provisioning/utils/version_loader.nu
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/env nu
|
||||
# Dynamic configuration loader for version management
|
||||
# Discovers and loads version configurations from the filesystem
|
||||
|
||||
use version_core.nu *
|
||||
|
||||
# Discover version configurations
|
||||
export def discover-configurations [
|
||||
--base-path: string = ""
|
||||
--types: list = [] # Filter by types
|
||||
]: nothing -> list {
|
||||
let base = if ($base_path | is-empty) {
|
||||
($env.PROVISIONING? | default $env.PWD)
|
||||
} else { $base_path }
|
||||
mut configurations = []
|
||||
|
||||
# Load from known version files directly
|
||||
let version_files = [
|
||||
($base | path join "versions.yaml")
|
||||
($base | path join "core" | path join "versions.yaml")
|
||||
]
|
||||
|
||||
for file in $version_files {
|
||||
if ($file | path exists) {
|
||||
let configs = (load-configuration-file $file)
|
||||
if ($configs | is-not-empty) {
|
||||
$configurations = ($configurations | append $configs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Also check providers directory
|
||||
let providers_path = ($base | path join "providers")
|
||||
if ($providers_path | path exists) {
|
||||
for provider_dir in (ls $providers_path | get name) {
|
||||
let version_file = ($provider_dir | path join "versions.yaml")
|
||||
if ($version_file | path exists) {
|
||||
let configs = (load-configuration-file $version_file)
|
||||
if ($configs | is-not-empty) {
|
||||
$configurations = ($configurations | append $configs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Filter by types if specified
|
||||
if ($types | length) > 0 {
|
||||
$configurations | where type in $types
|
||||
} else {
|
||||
$configurations
|
||||
}
|
||||
}
|
||||
|
||||
# Load configuration from file
|
||||
export def load-configuration-file [
|
||||
file_path: string
|
||||
]: nothing -> list {
|
||||
if not ($file_path | path exists) { return [] }
|
||||
|
||||
let ext = ($file_path | path parse | get extension)
|
||||
let parent_dir = ($file_path | path dirname)
|
||||
let context = (extract-context $parent_dir)
|
||||
|
||||
mut configs = []
|
||||
|
||||
match $ext {
|
||||
"yaml" | "yml" => {
|
||||
let data = (open $file_path)
|
||||
if ($data | describe | str contains "record") {
|
||||
# Convert record entries to configurations
|
||||
for item in ($data | transpose key value) {
|
||||
let config = (create-configuration $item.key $item.value $context $file_path)
|
||||
$configs = ($configs | append $config)
|
||||
}
|
||||
} else if ($data | describe | str contains "list") {
|
||||
# Already a list of configurations
|
||||
$configs = $data
|
||||
}
|
||||
}
|
||||
"k" => {
|
||||
# Parse KCL files for version information
|
||||
let content = (open $file_path)
|
||||
let version_data = (extract-kcl-versions $content)
|
||||
for item in $version_data {
|
||||
let config = (create-configuration $item.name $item $context $file_path)
|
||||
$configs = ($configs | append $config)
|
||||
}
|
||||
}
|
||||
"toml" => {
|
||||
let data = (open $file_path)
|
||||
for section in ($data | transpose key value) {
|
||||
if ($section.value | get -o version | is-not-empty) {
|
||||
let config = (create-configuration $section.key $section.value $context $file_path)
|
||||
$configs = ($configs | append $config)
|
||||
}
|
||||
}
|
||||
}
|
||||
"json" => {
|
||||
let data = (open $file_path)
|
||||
if ($data | get -o components | is-not-empty) {
|
||||
$configs = $data.components
|
||||
} else {
|
||||
# Treat as single configuration
|
||||
$configs = [$data]
|
||||
}
|
||||
}
|
||||
_ => []
|
||||
}
|
||||
|
||||
$configs
|
||||
}
|
||||
|
||||
# Extract context from path
|
||||
export def extract-context [
|
||||
dir_path: string
|
||||
]: nothing -> record {
|
||||
let parts = ($dir_path | split row "/")
|
||||
|
||||
# Determine type based on path structure
|
||||
let type = if ($parts | any { |p| $p == "providers" }) {
|
||||
"provider"
|
||||
} else if ($parts | any { |p| $p == "taskservs" }) {
|
||||
"taskserv"
|
||||
} else if ($parts | any { |p| $p == "clusters" }) {
|
||||
"cluster"
|
||||
} else if ($parts | any { |p| $p == "tools" }) {
|
||||
"tool"
|
||||
} else {
|
||||
"generic"
|
||||
}
|
||||
|
||||
# Extract category/subcategory
|
||||
let category = if $type == "provider" {
|
||||
$parts | skip while { |p| $p != "providers" } | skip 1 | first
|
||||
} else if $type == "taskserv" {
|
||||
$parts | skip while { |p| $p != "taskservs" } | skip 1 | first
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
{
|
||||
type: $type
|
||||
category: $category
|
||||
path: $dir_path
|
||||
}
|
||||
}
|
||||
|
||||
# Create configuration object
|
||||
export def create-configuration [
|
||||
id: string
|
||||
data: record
|
||||
context: record
|
||||
source_file: string
|
||||
]: nothing -> record {
|
||||
# Build detector configuration
|
||||
let detector = if ($data | get -o check_cmd | is-not-empty) {
|
||||
{
|
||||
method: "command"
|
||||
command: $data.check_cmd
|
||||
pattern: ($data | get -o parse_pattern | default "")
|
||||
capture: ($data | get -o capture_group | default "version")
|
||||
}
|
||||
} else if ($data | get -o detector | is-not-empty) {
|
||||
$data.detector
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
|
||||
# Build source configuration
|
||||
let source = if ($data | get -o source | is-not-empty) {
|
||||
if ($data.source | str contains "github.com") {
|
||||
{
|
||||
type: "github"
|
||||
repo: ($data.source | parse -r 'github\.com/(?<repo>.+)' | get -o 0 | get -o repo | str replace -r '/(releases|tags).*$' '')
|
||||
}
|
||||
} else if ($data.source | str starts-with "docker") {
|
||||
{
|
||||
type: "docker"
|
||||
image: ($data.source | str replace "docker://" "")
|
||||
}
|
||||
} else if ($data.source | str starts-with "http") {
|
||||
{
|
||||
type: "url"
|
||||
url: $data.source
|
||||
field: ($data | get -o version_field | default "")
|
||||
}
|
||||
} else {
|
||||
{ type: "custom", config: $data.source }
|
||||
}
|
||||
} else if ($data | get -o tags | is-not-empty) {
|
||||
# Infer from tags URL
|
||||
if ($data.tags | str contains "github") {
|
||||
{
|
||||
type: "github"
|
||||
repo: ($data.tags | parse -r 'github\.com/(?<repo>[^/]+/[^/]+)' | get -o 0 | get -o repo)
|
||||
}
|
||||
} else {
|
||||
{ type: "url", url: $data.tags }
|
||||
}
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
|
||||
# Build complete configuration
|
||||
{
|
||||
id: $id
|
||||
type: $context.type
|
||||
category: ($context.category | default "")
|
||||
version: ($data | get -o version | default "")
|
||||
fixed: ($data | get -o fixed | default false)
|
||||
source: $source
|
||||
detector: $detector
|
||||
comparison: ($data | get -o comparison | default "semantic")
|
||||
metadata: {
|
||||
source_file: $source_file
|
||||
site: ($data | get -o site | default "")
|
||||
description: ($data | get -o description | default "")
|
||||
install_cmd: ($data | get -o install_cmd | default "")
|
||||
lib: ($data | get -o lib | default "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Extract version info from KCL content
|
||||
export def extract-kcl-versions [
|
||||
content: string
|
||||
]: nothing -> list {
|
||||
mut versions = []
|
||||
|
||||
# Look for schema definitions with version fields
|
||||
let lines = ($content | lines)
|
||||
mut current_schema = ""
|
||||
mut current_data = {}
|
||||
|
||||
for line in $lines {
|
||||
if ($line | str contains "schema ") {
|
||||
# New schema found
|
||||
if ($current_schema | is-not-empty) and ($current_data | get -o version | is-not-empty) {
|
||||
$versions = ($versions | append {
|
||||
name: $current_schema
|
||||
...$current_data
|
||||
})
|
||||
}
|
||||
$current_schema = ($line | parse -r 'schema\s+(\w+)' | get -o 0 | get -o 0 | default "")
|
||||
$current_data = {}
|
||||
} else if ($line | str contains "version:") or ($line | str contains "version =") {
|
||||
# Extract version
|
||||
let version = ($line | parse -r 'version[:\s=]+"?([^"]+)"?' | get -o 0 | get -o 0 | default "")
|
||||
if ($version | is-not-empty) {
|
||||
$current_data.version = $version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add last schema if valid
|
||||
if ($current_schema | is-not-empty) and ($current_data | get -o version | is-not-empty) {
|
||||
$versions = ($versions | append {
|
||||
name: $current_schema
|
||||
...$current_data
|
||||
})
|
||||
}
|
||||
|
||||
$versions
|
||||
}
|
||||
217
core/nulib/lib_provisioning/utils/version_manager.nu
Normal file
217
core/nulib/lib_provisioning/utils/version_manager.nu
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env nu
|
||||
# Main version management interface
|
||||
# Completely configuration-driven, no hardcoded components
|
||||
|
||||
use version_core.nu *
|
||||
use version_loader.nu *
|
||||
use version_formatter.nu *
|
||||
use interface.nu *
|
||||
|
||||
# Check versions for discovered components
|
||||
export def check-versions [
|
||||
--path: string = "" # Base path to search
|
||||
--types: list = [] # Filter by types
|
||||
--fetch-latest = false # Fetch latest versions
|
||||
--respect-fixed = true # Respect fixed flag
|
||||
--config-file: string = "" # Use specific config file
|
||||
]: nothing -> list {
|
||||
# Load configurations
|
||||
let configs = if ($config_file | is-not-empty) {
|
||||
load-configuration-file $config_file
|
||||
} else {
|
||||
discover-configurations --base-path=$path --types=$types
|
||||
}
|
||||
|
||||
# Check each configuration
|
||||
$configs | each { |config|
|
||||
check-version $config --fetch-latest=$fetch_latest --respect-fixed=$respect_fixed
|
||||
}
|
||||
}
|
||||
|
||||
# Display version status
|
||||
export def show-versions [
|
||||
--path: string = ""
|
||||
--types: list = []
|
||||
--fetch-latest = true
|
||||
--group-by: string = "type"
|
||||
--format: string = "table" # table, json, yaml
|
||||
]: nothing -> nothing {
|
||||
let results = (check-versions --path=$path --types=$types --fetch-latest=$fetch_latest)
|
||||
|
||||
match $format {
|
||||
"table" => {
|
||||
format-results $results --group-by=$group_by
|
||||
}
|
||||
"json" => {
|
||||
print ($results | to json -i 2)
|
||||
}
|
||||
"yaml" => {
|
||||
print ($results | to yaml)
|
||||
}
|
||||
_ => {
|
||||
format-results $results
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check for available updates (does not modify configs)
|
||||
export def check-available-updates [
|
||||
--path: string = ""
|
||||
--types: list = []
|
||||
]: nothing -> nothing {
|
||||
let results = (check-versions --path=$path --types=$types --fetch-latest=true --respect-fixed=true)
|
||||
let updates = ($results | where status == "update_available")
|
||||
|
||||
if ($updates | is-empty) {
|
||||
_print "✅ All components are up to date"
|
||||
return
|
||||
}
|
||||
|
||||
_print "Updates available:"
|
||||
_print ($updates | select id configured latest | rename id configured "latest available" | table)
|
||||
|
||||
# Show installation guidance for each update
|
||||
for update in $updates {
|
||||
let config = (discover-configurations --types=[$update.type]
|
||||
| where id == $update.id
|
||||
| get -o 0)
|
||||
|
||||
if ($config | is-not-empty) {
|
||||
show-installation-guidance $config $update.latest
|
||||
}
|
||||
}
|
||||
|
||||
_print $"\n💡 After installing, run 'tools apply-updates' to update configuration files"
|
||||
}
|
||||
|
||||
# Apply updates to configuration files (after manual installation)
|
||||
export def apply-config-updates [
|
||||
--path: string = ""
|
||||
--types: list = []
|
||||
--dry-run = false
|
||||
--force = false # Update even if fixed
|
||||
]: nothing -> nothing {
|
||||
let results = (check-versions --path=$path --types=$types --fetch-latest=false --respect-fixed=(not $force))
|
||||
|
||||
# Find components where installed version is newer than configured
|
||||
let updates = ($results | where status == "ahead_config")
|
||||
|
||||
if ($updates | is-empty) {
|
||||
_print "✅ All configurations match installed versions"
|
||||
return
|
||||
}
|
||||
|
||||
_print "Configuration updates available (installed version newer than configured):"
|
||||
_print ($updates | select id configured installed | table)
|
||||
|
||||
if $dry_run {
|
||||
_print "\n🔍 Dry run mode - no changes will be made"
|
||||
return
|
||||
}
|
||||
|
||||
let proceed = (input "Update configurations to match installed versions? (y/n): ")
|
||||
if $proceed != "y" { return }
|
||||
|
||||
# Update each component's configuration file to match installed version
|
||||
for update in $updates {
|
||||
let config = (discover-configurations --types=[$update.type]
|
||||
| where id == $update.id
|
||||
| get -o 0)
|
||||
|
||||
if ($config | is-not-empty) {
|
||||
let source_file = $config.metadata.source_file
|
||||
update-configuration-file $source_file $update.id $update.installed
|
||||
_print $"✅ Updated config ($update.id): ($update.configured) -> ($update.installed)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Show agnostic installation guidance
|
||||
export def show-installation-guidance [
|
||||
config: record
|
||||
version: string
|
||||
]: nothing -> nothing {
|
||||
_print $"\n📦 To install ($config.id) ($version):"
|
||||
|
||||
# Show documentation/site links from configuration
|
||||
if ($config.metadata.site | is-not-empty) {
|
||||
_print $" • Documentation: ($config.metadata.site)"
|
||||
}
|
||||
|
||||
# Show source repository if available
|
||||
if ($config.source.type? | default "" | str contains "github") {
|
||||
let repo = ($config.source.repo? | default "")
|
||||
if ($repo | is-not-empty) {
|
||||
_print $" • Releases: https://github.com/($repo)/releases"
|
||||
}
|
||||
}
|
||||
|
||||
# Show generic installation command if available in metadata
|
||||
if ($config.metadata.install_cmd? | default "" | is-not-empty) {
|
||||
_print $" • Install: ($config.metadata.install_cmd)"
|
||||
}
|
||||
|
||||
_print $"\n🔍 Configuration updated, manual installation required"
|
||||
_print $"💡 Run 'tools check ($config.id)' after installation to verify"
|
||||
}
|
||||
|
||||
# Update configuration file
|
||||
export def update-configuration-file [
|
||||
file_path: string
|
||||
component_id: string
|
||||
new_version: string
|
||||
]: nothing -> nothing {
|
||||
if not ($file_path | path exists) { return }
|
||||
|
||||
let ext = ($file_path | path parse | get extension)
|
||||
|
||||
match $ext {
|
||||
"yaml" | "yml" => {
|
||||
let data = (open $file_path)
|
||||
let updated = ($data | upsert $component_id ($data | get $component_id | upsert version $new_version))
|
||||
$updated | save -f $file_path
|
||||
}
|
||||
"json" => {
|
||||
let data = (open $file_path)
|
||||
let updated = ($data | upsert $component_id ($data | get $component_id | upsert version $new_version))
|
||||
$updated | to json -i 2 | save -f $file_path
|
||||
}
|
||||
"toml" => {
|
||||
# TOML update would need proper TOML writer
|
||||
print $"⚠️ TOML update not implemented for ($file_path)"
|
||||
}
|
||||
"k" => {
|
||||
# KCL update would need KCL parser/writer
|
||||
print $"⚠️ KCL update not implemented for ($file_path)"
|
||||
}
|
||||
_ => {
|
||||
print $"⚠️ Unknown file type: ($ext)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Pin/unpin component version
|
||||
export def set-fixed [
|
||||
component_id: string
|
||||
fixed: bool
|
||||
--path: string = ""
|
||||
]: nothing -> nothing {
|
||||
let configs = (discover-configurations --base-path=$path)
|
||||
let config = ($configs | where id == $component_id | get -o 0)
|
||||
|
||||
if ($config | is-empty) {
|
||||
print $"❌ Component '($component_id)' not found"
|
||||
return
|
||||
}
|
||||
|
||||
let source_file = $config.metadata.source_file
|
||||
let data = (open $source_file)
|
||||
let updated = ($data | upsert $component_id ($data | get $component_id | upsert fixed $fixed))
|
||||
$updated | save -f $source_file
|
||||
|
||||
if $fixed {
|
||||
print $"🔒 Pinned ($component_id) to version ($config.version)"
|
||||
} else {
|
||||
print $"🔓 Unpinned ($component_id)"
|
||||
}
|
||||
}
|
||||
235
core/nulib/lib_provisioning/utils/version_registry.nu
Normal file
235
core/nulib/lib_provisioning/utils/version_registry.nu
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/env nu
|
||||
# Version registry management for taskservs
|
||||
# Handles the central version registry and integrates with taskserv configurations
|
||||
|
||||
use version_core.nu *
|
||||
use version_taskserv.nu *
|
||||
use interface.nu *
|
||||
|
||||
# Load the version registry
|
||||
export def load-version-registry [
|
||||
--registry-file: string = ""
|
||||
]: nothing -> record {
|
||||
let registry_path = if ($registry_file | is-not-empty) {
|
||||
$registry_file
|
||||
} else {
|
||||
($env.PROVISIONING | path join "core" | path join "taskservs-versions.yaml")
|
||||
}
|
||||
|
||||
if not ($registry_path | path exists) {
|
||||
_print $"⚠️ Version registry not found: ($registry_path)"
|
||||
return {}
|
||||
}
|
||||
|
||||
open $registry_path
|
||||
}
|
||||
|
||||
# Update registry with latest version information
|
||||
export def update-registry-versions [
|
||||
--components: list = [] # Specific components to update, empty for all
|
||||
--dry-run = false
|
||||
]: nothing -> nothing {
|
||||
let registry = (load-version-registry)
|
||||
|
||||
if ($registry | is-empty) {
|
||||
_print "❌ Could not load version registry"
|
||||
return
|
||||
}
|
||||
|
||||
let components_to_update = if ($components | is-empty) {
|
||||
$registry | transpose key value | get key
|
||||
} else {
|
||||
$components
|
||||
}
|
||||
|
||||
_print $"Updating versions for ($components_to_update | length) components..."
|
||||
|
||||
for component in $components_to_update {
|
||||
let component_config = ($registry | get -o $component)
|
||||
|
||||
if ($component_config | is-empty) {
|
||||
_print $"⚠️ Component '($component)' not found in registry"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($component_config.fixed | default false) {
|
||||
_print $"🔒 Skipping pinned component: ($component)"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($component_config.source | is-empty) {
|
||||
_print $"⚠️ No source configured for: ($component)"
|
||||
continue
|
||||
}
|
||||
|
||||
_print $"🔍 Checking latest version for: ($component)"
|
||||
|
||||
let latest_versions = (fetch-versions $component_config.source --limit=5)
|
||||
if ($latest_versions | is-empty) {
|
||||
_print $"❌ Could not fetch versions for: ($component)"
|
||||
continue
|
||||
}
|
||||
|
||||
let latest = ($latest_versions | get 0)
|
||||
let current = ($component_config.current_version | default "")
|
||||
|
||||
if $latest != $current {
|
||||
_print $"📦 ($component): ($current) -> ($latest)"
|
||||
if not $dry_run {
|
||||
# Update registry with new version
|
||||
update-registry-component $component "current_version" $latest
|
||||
update-registry-component $component "latest_check" (date now | format date "%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
} else {
|
||||
_print $"✅ ($component): up to date at ($current)"
|
||||
}
|
||||
}
|
||||
|
||||
if not $dry_run {
|
||||
_print "✅ Registry update completed"
|
||||
} else {
|
||||
_print "🔍 Dry run completed - no changes made"
|
||||
}
|
||||
}
|
||||
|
||||
# Update a specific component field in the registry
|
||||
export def update-registry-component [
|
||||
component_id: string
|
||||
field: string
|
||||
value: string
|
||||
]: nothing -> nothing {
|
||||
let registry_path = ($env.PROVISIONING | path join "core" | path join "taskservs-versions.yaml")
|
||||
|
||||
if not ($registry_path | path exists) {
|
||||
_print $"❌ Registry file not found: ($registry_path)"
|
||||
return
|
||||
}
|
||||
|
||||
let registry = (open $registry_path)
|
||||
let component_config = ($registry | get -o $component_id)
|
||||
|
||||
if ($component_config | is-empty) {
|
||||
_print $"❌ Component '($component_id)' not found in registry"
|
||||
return
|
||||
}
|
||||
|
||||
let updated_component = ($component_config | upsert $field $value)
|
||||
let updated_registry = ($registry | upsert $component_id $updated_component)
|
||||
|
||||
$updated_registry | save -f $registry_path
|
||||
}
|
||||
|
||||
# Compare registry versions with taskserv configurations
|
||||
export def compare-registry-with-taskservs [
|
||||
--taskservs-path: string = ""
|
||||
]: nothing -> list {
|
||||
let registry = (load-version-registry)
|
||||
let taskserv_configs = (discover-taskserv-configurations --base-path=$taskservs_path)
|
||||
|
||||
if ($registry | is-empty) or ($taskserv_configs | is-empty) {
|
||||
_print "❌ Could not load registry or taskserv configurations"
|
||||
return []
|
||||
}
|
||||
|
||||
# Group taskservs by component type
|
||||
let taskserv_by_component = ($taskserv_configs | group-by { |config|
|
||||
# Extract component name from ID (handle both "component" and "server::component" formats)
|
||||
if ($config.id | str contains "::") {
|
||||
($config.id | split row "::" | get 1)
|
||||
} else {
|
||||
$config.id
|
||||
}
|
||||
})
|
||||
|
||||
let comparisons = ($registry | transpose component registry_config | each { |registry_item|
|
||||
let component = $registry_item.component
|
||||
let registry_version = ($registry_item.registry_config.current_version | default "")
|
||||
let taskservs = ($taskserv_by_component | get -o $component | default [])
|
||||
|
||||
if ($taskservs | is-empty) {
|
||||
{
|
||||
component: $component
|
||||
registry_version: $registry_version
|
||||
taskserv_configs: []
|
||||
status: "unused"
|
||||
summary: "Not used in any taskservs"
|
||||
}
|
||||
} else {
|
||||
let taskserv_versions = ($taskservs | each { |ts| {
|
||||
id: $ts.id
|
||||
version: $ts.version
|
||||
file: $ts.kcl_file
|
||||
matches_registry: ($ts.version == $registry_version)
|
||||
}})
|
||||
|
||||
let all_match = ($taskserv_versions | all { |ts| $ts.matches_registry })
|
||||
let any_outdated = ($taskserv_versions | any { |ts| not $ts.matches_registry })
|
||||
|
||||
let status = if $all_match {
|
||||
"in_sync"
|
||||
} else if $any_outdated {
|
||||
"out_of_sync"
|
||||
} else {
|
||||
"mixed"
|
||||
}
|
||||
|
||||
{
|
||||
component: $component
|
||||
registry_version: $registry_version
|
||||
taskserv_configs: $taskserv_versions
|
||||
status: $status
|
||||
summary: $"($taskserv_versions | length) taskservs, ($taskserv_versions | where matches_registry | length) in sync"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$comparisons
|
||||
}
|
||||
|
||||
# Show version status summary
|
||||
export def show-version-status [
|
||||
--taskservs-path: string = ""
|
||||
--format: string = "table" # table, detail, json
|
||||
]: nothing -> nothing {
|
||||
let comparisons = (compare-registry-with-taskservs --taskservs-path=$taskservs_path)
|
||||
|
||||
match $format {
|
||||
"table" => {
|
||||
_print "Taskserv Version Status:"
|
||||
_print ($comparisons | select component registry_version status summary | table)
|
||||
}
|
||||
"detail" => {
|
||||
for comparison in $comparisons {
|
||||
_print $"\n🔧 ($comparison.component) \\(Registry: ($comparison.registry_version)\\)"
|
||||
_print $" Status: ($comparison.status) - ($comparison.summary)"
|
||||
|
||||
if ($comparison.taskserv_configs | length) > 0 {
|
||||
for config in $comparison.taskserv_configs {
|
||||
let status_icon = if $config.matches_registry { "✅" } else { "❌" }
|
||||
_print $" ($status_icon) ($config.id): ($config.version)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"json" => {
|
||||
print ($comparisons | to json -i 2)
|
||||
}
|
||||
_ => {
|
||||
_print $"❌ Unknown format: ($format). Use 'table', 'detail', or 'json'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Pin/unpin component in registry
|
||||
export def set-registry-fixed [
|
||||
component_id: string
|
||||
fixed: bool
|
||||
]: nothing -> nothing {
|
||||
update-registry-component $component_id "fixed" ($fixed | into string)
|
||||
|
||||
if $fixed {
|
||||
_print $"🔒 Pinned ($component_id) in registry"
|
||||
} else {
|
||||
_print $"🔓 Unpinned ($component_id) in registry"
|
||||
}
|
||||
}
|
||||
277
core/nulib/lib_provisioning/utils/version_taskserv.nu
Normal file
277
core/nulib/lib_provisioning/utils/version_taskserv.nu
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
#!/usr/bin/env nu
|
||||
# Taskserv version extraction and management utilities
|
||||
# Handles KCL taskserv files and version configuration
|
||||
|
||||
use version_core.nu *
|
||||
use version_loader.nu *
|
||||
use interface.nu *
|
||||
|
||||
# Extract version field from KCL taskserv files
|
||||
export def extract-kcl-version [
|
||||
file_path: string
|
||||
]: nothing -> string {
|
||||
if not ($file_path | path exists) { return "" }
|
||||
|
||||
let content = (open $file_path --raw)
|
||||
|
||||
# Look for version assignment in taskserv configuration files
|
||||
let version_matches = ($content | lines | each { |line|
|
||||
let trimmed_line = ($line | str trim)
|
||||
# Match "version = " pattern (but not major_version, cni_version, etc.)
|
||||
if ($trimmed_line | str starts-with "version") and ($trimmed_line | str contains "=") {
|
||||
# Split on equals and take the right side
|
||||
let parts = ($trimmed_line | split row "=")
|
||||
if ($parts | length) >= 2 {
|
||||
let version_value = ($parts | get 1 | str trim)
|
||||
if ($version_value | str starts-with '"') {
|
||||
# Remove quotes and get the value
|
||||
($version_value | parse -r '"([^"]*)"' | get -o 0.capture0 | default "")
|
||||
} else if ($version_value | str starts-with "'") {
|
||||
# Handle single quotes
|
||||
($version_value | parse -r "'([^']*)'" | get -o 0.capture0 | default "")
|
||||
} else {
|
||||
# Handle unquoted values (remove any trailing comments)
|
||||
($version_value | str replace "\\s*#.*$" "" | str trim)
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else if ($trimmed_line | str starts-with "version:") and not ($trimmed_line | str contains "str") {
|
||||
# Handle schema-style "version: value" (not type declarations)
|
||||
let version_part = ($trimmed_line | str replace "version:\\s*" "")
|
||||
if ($version_part | str starts-with '"') {
|
||||
($version_part | parse -r '"([^"]*)"' | get -o 0.capture0 | default "")
|
||||
} else if ($version_part | str starts-with "'") {
|
||||
($version_part | parse -r "'([^']*)'" | get -o 0.capture0 | default "")
|
||||
} else {
|
||||
($version_part | str replace "\\s*#.*$" "" | str trim)
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} | where { |v| $v != "" })
|
||||
|
||||
if ($version_matches | length) > 0 {
|
||||
$version_matches | get 0
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
# Discover all taskserv KCL files and their versions
|
||||
export def discover-taskserv-configurations [
|
||||
--base-path: string = ""
|
||||
]: nothing -> list {
|
||||
let taskservs_path = if ($base_path | is-not-empty) {
|
||||
$base_path
|
||||
} else {
|
||||
$env.PROVISIONING_TASKSERVS_PATH
|
||||
}
|
||||
|
||||
if not ($taskservs_path | path exists) {
|
||||
_print $"⚠️ Taskservs path not found: ($taskservs_path)"
|
||||
return []
|
||||
}
|
||||
|
||||
# Find all .k files recursively in the taskservs directory
|
||||
let all_k_files = (glob $"($taskservs_path)/**/*.k")
|
||||
|
||||
let kcl_configs = ($all_k_files | each { |kcl_file|
|
||||
let version = (extract-kcl-version $kcl_file)
|
||||
if ($version | is-not-empty) {
|
||||
let relative_path = ($kcl_file | str replace $"($taskservs_path)/" "")
|
||||
let path_parts = ($relative_path | split row "/" | where { |p| $p != "" })
|
||||
|
||||
# Determine ID from the path structure
|
||||
let id = if ($path_parts | length) >= 2 {
|
||||
# If it's a server-specific file like "wuji-strg-1/kubernetes.k"
|
||||
let filename = ($kcl_file | path basename | str replace ".k" "")
|
||||
$"($path_parts.0)::($filename)"
|
||||
} else {
|
||||
# If it's a general file like "proxy.k"
|
||||
($kcl_file | path basename | str replace ".k" "")
|
||||
}
|
||||
|
||||
{
|
||||
id: $id
|
||||
type: "taskserv"
|
||||
kcl_file: $kcl_file
|
||||
version: $version
|
||||
metadata: {
|
||||
source_file: $kcl_file
|
||||
category: "taskserv"
|
||||
path_structure: $path_parts
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} | where { |item| $item != null })
|
||||
|
||||
$kcl_configs
|
||||
}
|
||||
|
||||
# Update version in KCL file
|
||||
export def update-kcl-version [
|
||||
file_path: string
|
||||
new_version: string
|
||||
]: nothing -> nothing {
|
||||
if not ($file_path | path exists) {
|
||||
_print $"❌ File not found: ($file_path)"
|
||||
return
|
||||
}
|
||||
|
||||
let content = (open $file_path --raw)
|
||||
|
||||
# Replace version field while preserving formatting
|
||||
let updated_content = ($content | lines | each { |line|
|
||||
if ($line | str trim | str starts-with "version:") {
|
||||
# Preserve indentation and update version
|
||||
let indent = ($line | str replace "^(\\s*).*" '$1')
|
||||
let line_trimmed = ($line | str trim)
|
||||
if ($line_trimmed | str contains '"') {
|
||||
$"($indent)version: \"($new_version)\""
|
||||
} else if ($line_trimmed | str contains "'") {
|
||||
$"($indent)version: '($new_version)'"
|
||||
} else {
|
||||
$"($indent)version: str = \"($new_version)\""
|
||||
}
|
||||
} else {
|
||||
$line
|
||||
}
|
||||
} | str join "\n")
|
||||
|
||||
$updated_content | save -f $file_path
|
||||
_print $"✅ Updated version in ($file_path) to ($new_version)"
|
||||
}
|
||||
|
||||
# Check taskserv versions against available versions
|
||||
export def check-taskserv-versions [
|
||||
--fetch-latest = false
|
||||
]: nothing -> list {
|
||||
let configs = (discover-taskserv-configurations)
|
||||
|
||||
if ($configs | is-empty) {
|
||||
_print "No taskserv configurations found"
|
||||
return []
|
||||
}
|
||||
|
||||
$configs | each { |config|
|
||||
# For now, return basic info - can be extended with version checking logic
|
||||
{
|
||||
id: $config.id
|
||||
type: $config.type
|
||||
configured: $config.version
|
||||
kcl_file: $config.kcl_file
|
||||
status: "configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Update taskserv version in KCL file
|
||||
export def update-taskserv-version [
|
||||
taskserv_id: string
|
||||
new_version: string
|
||||
--dry-run = false
|
||||
]: nothing -> nothing {
|
||||
let configs = (discover-taskserv-configurations)
|
||||
let config = ($configs | where id == $taskserv_id | get -o 0)
|
||||
|
||||
if ($config | is-empty) {
|
||||
_print $"❌ Taskserv '($taskserv_id)' not found"
|
||||
return
|
||||
}
|
||||
|
||||
if $dry_run {
|
||||
_print $"🔍 Would update ($taskserv_id) from ($config.version) to ($new_version) in ($config.kcl_file)"
|
||||
return
|
||||
}
|
||||
|
||||
update-kcl-version $config.kcl_file $new_version
|
||||
}
|
||||
|
||||
# Bulk update multiple taskservs
|
||||
export def bulk-update-taskservs [
|
||||
updates: list # List of {id: string, version: string}
|
||||
--dry-run = false
|
||||
]: nothing -> nothing {
|
||||
if ($updates | is-empty) {
|
||||
_print "No updates provided"
|
||||
return
|
||||
}
|
||||
|
||||
_print $"Updating ($updates | length) taskservs..."
|
||||
|
||||
for update in $updates {
|
||||
let taskserv_id = ($update | get -o id | default "")
|
||||
let new_version = ($update | get -o version | default "")
|
||||
|
||||
if ($taskserv_id | is-empty) or ($new_version | is-empty) {
|
||||
_print $"⚠️ Invalid update entry: ($update)"
|
||||
continue
|
||||
}
|
||||
|
||||
update-taskserv-version $taskserv_id $new_version --dry-run=$dry_run
|
||||
}
|
||||
|
||||
if not $dry_run {
|
||||
_print "✅ Bulk update completed"
|
||||
}
|
||||
}
|
||||
|
||||
# Sync taskserv versions with registry
|
||||
export def taskserv-sync-versions [
|
||||
--taskservs-path: string = ""
|
||||
--component: string = "" # Specific component to sync
|
||||
--dry-run = false
|
||||
]: nothing -> nothing {
|
||||
let registry = (load-version-registry)
|
||||
let comparisons = (compare-registry-with-taskservs --taskservs-path=$taskservs_path)
|
||||
|
||||
if ($comparisons | is-empty) {
|
||||
_print "❌ No taskserv configurations found"
|
||||
return
|
||||
}
|
||||
|
||||
# Filter to out-of-sync components
|
||||
mut out_of_sync = ($comparisons | where status == "out_of_sync")
|
||||
|
||||
if ($component | is-not-empty) {
|
||||
let filtered = ($out_of_sync | where component == $component)
|
||||
if ($filtered | is-empty) {
|
||||
_print $"✅ Component '($component)' is already in sync or not found"
|
||||
return
|
||||
}
|
||||
$out_of_sync = $filtered
|
||||
}
|
||||
|
||||
if ($out_of_sync | is-empty) {
|
||||
_print "✅ All taskservs are in sync with registry"
|
||||
return
|
||||
}
|
||||
|
||||
_print $"Found ($out_of_sync | length) components with version mismatches:"
|
||||
|
||||
for comp in $out_of_sync {
|
||||
_print $"\n🔧 ($comp.component) [Registry: ($comp.registry_version)]"
|
||||
|
||||
# Find taskservs that need updating
|
||||
let outdated_taskservs = ($comp.taskserv_configs | where not matches_registry)
|
||||
|
||||
for taskserv in $outdated_taskservs {
|
||||
if $dry_run {
|
||||
_print $"🔍 Would update ($taskserv.id): ($taskserv.version) -> ($comp.registry_version)"
|
||||
} else {
|
||||
_print $"🔄 Updating ($taskserv.id): ($taskserv.version) -> ($comp.registry_version)"
|
||||
update-kcl-version $taskserv.file $comp.registry_version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if $dry_run {
|
||||
_print "\n🔍 Dry run completed - no changes made"
|
||||
} else {
|
||||
_print "\n✅ Sync completed"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue