feat(taskserv): implement real-time version checking with configurable HTTP client
- Add: GitHub API integration for live version checking in taskserv management - Add: HTTP client configuration option (http.use_curl) in config.defaults.toml - Add: Helper function fetch_latest_version with curl/http get support - Fix: Settings path structure for prov_data_dirpath access pattern - Remove: Legacy simulation code for version checking - Update: Core configuration name from "provisioning-system" to "provisioning" - Clean: Remove obsolete example configs and infrastructure files
This commit is contained in:
parent
38a7470da0
commit
3c3ef47f7f
34 changed files with 5942 additions and 13 deletions
|
|
@ -14,6 +14,7 @@ export use secrets.nu *
|
|||
export use ai.nu *
|
||||
export use contexts.nu *
|
||||
export use extensions.nu *
|
||||
export use taskserv.nu *
|
||||
#export use main.nu *
|
||||
|
||||
# export use server.nu *
|
||||
|
|
|
|||
400
core/nulib/main_provisioning/taskserv.nu
Normal file
400
core/nulib/main_provisioning/taskserv.nu
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
# Taskserv Management Commands
|
||||
# Purpose: Main interface for taskserv version management and operations
|
||||
# PAP Compliance: Config-driven, no hardcoding, graceful periods
|
||||
|
||||
use lib_provisioning *
|
||||
|
||||
# Main taskserv command dispatcher
|
||||
export def "main taskserv" [
|
||||
command: string # Subcommand: versions, check-updates, update, pin, unpin
|
||||
...args # Additional arguments
|
||||
--help(-h) # Show help
|
||||
]: nothing -> any {
|
||||
if $help {
|
||||
show_taskserv_help
|
||||
return
|
||||
}
|
||||
|
||||
match $command {
|
||||
"versions" => {
|
||||
if ($args | length) > 0 {
|
||||
show_taskserv_versions ($args | get 0)
|
||||
} else {
|
||||
show_taskserv_versions
|
||||
}
|
||||
}
|
||||
"check-updates" => {
|
||||
if ($args | length) > 0 {
|
||||
check_taskserv_updates ($args | get 0)
|
||||
} else {
|
||||
check_taskserv_updates
|
||||
}
|
||||
}
|
||||
"update" => {
|
||||
print "Feature not implemented yet. Available commands: versions"
|
||||
}
|
||||
"pin" => {
|
||||
print "Feature not implemented yet. Available commands: versions"
|
||||
}
|
||||
"unpin" => {
|
||||
print "Feature not implemented yet. Available commands: versions"
|
||||
}
|
||||
_ => {
|
||||
print $"Unknown taskserv command: ($command)"
|
||||
show_taskserv_help
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def show_taskserv_versions [name?: string] {
|
||||
use ../lib_provisioning/config/accessor.nu get-taskservs-path
|
||||
|
||||
print "📦 Taskserv Versions:"
|
||||
print ""
|
||||
|
||||
let taskservs_path = (get-taskservs-path)
|
||||
|
||||
if not ($taskservs_path | path exists) {
|
||||
print $"⚠️ Taskservs path not found: ($taskservs_path)"
|
||||
return
|
||||
}
|
||||
|
||||
# Find all KCL files in taskservs
|
||||
let all_k_files = (glob $"($taskservs_path)/**/*.k")
|
||||
|
||||
let all_taskservs = ($all_k_files | each { |kcl_file|
|
||||
# Skip __init__.k, schema files, and other utility files
|
||||
if ($kcl_file | str ends-with "__init__.k") or ($kcl_file | str contains "/wrks/") or ($kcl_file | str ends-with "taskservs/version.k") {
|
||||
null
|
||||
} else {
|
||||
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) >= 3 {
|
||||
# Like "containerd/default/kcl/containerd.k"
|
||||
$path_parts.0
|
||||
} else if ($path_parts | length) == 2 {
|
||||
# Like "proxy/kcl/proxy.k" or special cases
|
||||
let filename = ($kcl_file | path basename | str replace ".k" "")
|
||||
if $path_parts.0 == "no" {
|
||||
$"($path_parts.0)::($filename)"
|
||||
} else {
|
||||
$path_parts.0
|
||||
}
|
||||
} else {
|
||||
($kcl_file | path basename | str replace ".k" "")
|
||||
}
|
||||
|
||||
# Try to read from version.k file first, then fallback to schema extraction
|
||||
let version_file = ($kcl_file | path dirname | path join "version.k")
|
||||
let version = if ($version_file | path exists) {
|
||||
# Read version from version.k file using KCL
|
||||
let kcl_result = (^kcl $version_file | complete)
|
||||
if $kcl_result.exit_code == 0 and ($kcl_result.stdout | is-not-empty) {
|
||||
let result = ($kcl_result.stdout | from yaml)
|
||||
# Try new TaskservVersion schema format first (direct output structure)
|
||||
if ($result | get -o version.current | is-not-empty) {
|
||||
# New TaskservVersion schema format - direct structure
|
||||
($result | get version.current)
|
||||
} else if ($result | get -o current | is-not-empty) {
|
||||
# Simple format for backward compatibility
|
||||
($result | get current)
|
||||
} else {
|
||||
# Fallback to legacy naming convention
|
||||
let clean_id = ($id | split row "::" | last)
|
||||
let version_key = $"($clean_id)_version"
|
||||
let legacy_version_data = ($result | get -o $version_key | default {})
|
||||
if ($legacy_version_data | get -o version.current | is-not-empty) {
|
||||
($legacy_version_data | get version.current)
|
||||
} else if ($legacy_version_data | get -o current | is-not-empty) {
|
||||
($legacy_version_data | get current)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
# Fallback to schema extraction for files without version.k
|
||||
use ../lib_provisioning/utils/version_taskserv.nu extract-kcl-version
|
||||
extract-kcl-version $kcl_file
|
||||
}
|
||||
|
||||
{
|
||||
id: $id
|
||||
version: (if ($version | is-not-empty) { $version } else { "not defined" })
|
||||
file: $kcl_file
|
||||
has_version: ($version | is-not-empty)
|
||||
}
|
||||
}
|
||||
} | where $it != null)
|
||||
|
||||
# Remove duplicates and sort
|
||||
let unique_taskservs = ($all_taskservs | group-by id | items { |key, items|
|
||||
{
|
||||
id: $key
|
||||
version: ($items | where has_version | get -o 0.version | default "not defined")
|
||||
has_version: ($items | any { |item| $item.has_version })
|
||||
}
|
||||
} | sort-by id)
|
||||
|
||||
let filtered = if ($name | is-not-empty) {
|
||||
$unique_taskservs | where id =~ $name
|
||||
} else {
|
||||
$unique_taskservs
|
||||
}
|
||||
|
||||
if ($filtered | is-empty) {
|
||||
print $"No taskserv found matching: ($name)"
|
||||
return
|
||||
}
|
||||
|
||||
# Show with version status
|
||||
$filtered | each { |taskserv|
|
||||
let status = if $taskserv.has_version { "✅" } else { "⚠️" }
|
||||
print $" ($status) ($taskserv.id): ($taskserv.version)"
|
||||
}
|
||||
|
||||
print ""
|
||||
let with_versions = ($filtered | where has_version | length)
|
||||
let without_versions = ($filtered | where (not has_version) | length)
|
||||
print $"Found ($filtered | length) taskservs"
|
||||
print $" - ($with_versions) with versions defined"
|
||||
print $" - ($without_versions) without versions"
|
||||
}
|
||||
|
||||
def show_taskserv_help [] {
|
||||
print "Taskserv Management Commands:"
|
||||
print ""
|
||||
print " versions [name] - List taskserv versions"
|
||||
print " check-updates [name] - Check for available updates"
|
||||
print " update <name> <ver> - Update taskserv to specific version"
|
||||
print " pin <name> - Pin taskserv version (disable updates)"
|
||||
print " unpin <name> - Unpin taskserv version (enable updates)"
|
||||
print ""
|
||||
print "Examples:"
|
||||
print " provisioning taskserv versions # List all versions"
|
||||
print " provisioning taskserv versions kubernetes # Show kubernetes version"
|
||||
print " provisioning taskserv check-updates # Check all for updates"
|
||||
print " provisioning taskserv update kubernetes 1.31.2 # Update kubernetes"
|
||||
print " provisioning taskserv pin kubernetes # Pin kubernetes version"
|
||||
}
|
||||
|
||||
# Check for taskserv updates
|
||||
# Helper function to fetch latest version from GitHub API
|
||||
def fetch_latest_version [api_url: string, fallback: string, use_curl: bool]: nothing -> string {
|
||||
if $use_curl {
|
||||
let fetch_result = ^curl -s $api_url | complete
|
||||
if $fetch_result.exit_code == 0 {
|
||||
let response = $fetch_result.stdout | from json
|
||||
$response.tag_name | str replace "^v" ""
|
||||
} else {
|
||||
$fallback
|
||||
}
|
||||
} else {
|
||||
let response = (http get $api_url --headers [User-Agent "provisioning-version-checker"])
|
||||
let response_version = ($response | get -o tag_name)
|
||||
if ($response_version | is-not-empty ) {
|
||||
$response_version | str replace "^v" ""
|
||||
} else {
|
||||
$fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def check_taskserv_updates [
|
||||
taskserv_name?: string # Optional specific taskserv name
|
||||
]: nothing -> nothing {
|
||||
use ../lib_provisioning/config/accessor.nu get-taskservs-path
|
||||
use ../lib_provisioning/config/accessor.nu get-config
|
||||
use ../lib_provisioning/config/loader.nu get-config-value
|
||||
|
||||
print "🔄 Checking for taskserv updates..."
|
||||
print ""
|
||||
|
||||
let taskservs_path = (get-taskservs-path)
|
||||
|
||||
if not ($taskservs_path | path exists) {
|
||||
print $"⚠️ Taskservs path not found: ($taskservs_path)"
|
||||
return
|
||||
}
|
||||
|
||||
# Get all taskservs (same logic as show_taskserv_versions)
|
||||
let all_k_files = (glob $"($taskservs_path)/**/*.k")
|
||||
|
||||
let all_taskservs = ($all_k_files | each { |kcl_file|
|
||||
# Skip __init__.k, schema files, and other utility files
|
||||
if ($kcl_file | str ends-with "__init__.k") or ($kcl_file | str contains "/wrks/") or ($kcl_file | str ends-with "taskservs/version.k") {
|
||||
null
|
||||
} else {
|
||||
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) >= 3 {
|
||||
$path_parts.0
|
||||
} else if ($path_parts | length) == 2 {
|
||||
let filename = ($kcl_file | path basename | str replace ".k" "")
|
||||
if $path_parts.0 == "no" {
|
||||
$"($path_parts.0)::($filename)"
|
||||
} else {
|
||||
$path_parts.0
|
||||
}
|
||||
} else {
|
||||
($kcl_file | path basename | str replace ".k" "")
|
||||
}
|
||||
|
||||
# Read version data from version.k file
|
||||
let version_file = ($kcl_file | path dirname | path join "version.k")
|
||||
let version_info = if ($version_file | path exists) {
|
||||
let kcl_result = (^kcl $version_file | complete)
|
||||
if $kcl_result.exit_code == 0 and ($kcl_result.stdout | is-not-empty) {
|
||||
let result = ($kcl_result.stdout | from yaml)
|
||||
{
|
||||
current: ($result | get -o version.current | default "")
|
||||
source: ($result | get -o version.source | default "")
|
||||
check_latest: ($result | get -o version.check_latest | default false)
|
||||
has_version: true
|
||||
}
|
||||
} else {
|
||||
{
|
||||
current: ""
|
||||
source: ""
|
||||
check_latest: false
|
||||
has_version: false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
{
|
||||
current: ""
|
||||
source: ""
|
||||
check_latest: false
|
||||
has_version: false
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
id: $id
|
||||
current_version: $version_info.current
|
||||
source_url: $version_info.source
|
||||
check_latest: $version_info.check_latest
|
||||
has_version: $version_info.has_version
|
||||
}
|
||||
}
|
||||
} | where $it != null)
|
||||
|
||||
# Filter to unique taskservs and optionally filter by name
|
||||
let unique_taskservs = ($all_taskservs
|
||||
| group-by id
|
||||
| items { |key, items|
|
||||
{
|
||||
id: $key
|
||||
current_version: ($items | where has_version | get -o 0.current_version | default "not defined")
|
||||
source_url: ($items | where has_version | get -o 0.source_url | default "")
|
||||
check_latest: ($items | where has_version | get -o 0.check_latest | default false)
|
||||
has_version: ($items | any { |item| $item.has_version })
|
||||
}
|
||||
}
|
||||
| sort-by id
|
||||
| if ($taskserv_name | is-not-empty) {
|
||||
where id == $taskserv_name
|
||||
} else {
|
||||
$in
|
||||
}
|
||||
)
|
||||
|
||||
if ($unique_taskservs | is-empty) {
|
||||
if ($taskserv_name | is-not-empty) {
|
||||
print $"❌ Taskserv '($taskserv_name)' not found"
|
||||
} else {
|
||||
print "❌ No taskservs found"
|
||||
}
|
||||
return
|
||||
}
|
||||
let config = get-config
|
||||
let use_curl = (get-config-value $config "http.use_curl" false)
|
||||
# Check updates for each taskserv
|
||||
let update_results = ($unique_taskservs | each { |taskserv|
|
||||
if not $taskserv.has_version {
|
||||
{
|
||||
id: $taskserv.id
|
||||
status: "no_version"
|
||||
current: "not defined"
|
||||
latest: ""
|
||||
update_available: false
|
||||
message: "No version defined"
|
||||
}
|
||||
} else if not $taskserv.check_latest {
|
||||
{
|
||||
id: $taskserv.id
|
||||
status: "pinned"
|
||||
current: $taskserv.current_version
|
||||
latest: ""
|
||||
update_available: false
|
||||
message: "Version pinned (check_latest = false)"
|
||||
}
|
||||
} else if ($taskserv.source_url | is-empty) {
|
||||
{
|
||||
id: $taskserv.id
|
||||
status: "no_source"
|
||||
current: $taskserv.current_version
|
||||
latest: ""
|
||||
update_available: false
|
||||
message: "No source URL for update checking"
|
||||
}
|
||||
} else {
|
||||
# Fetch latest version from GitHub releases API
|
||||
let api_url = $taskserv.source_url | str replace "github.com" "api.github.com/repos" | str replace "/releases" "/releases/latest"
|
||||
let latest_version = if ($taskserv.source_url | is-empty) {
|
||||
$taskserv.current_version
|
||||
} else {
|
||||
fetch_latest_version $api_url $taskserv.current_version $use_curl
|
||||
}
|
||||
let update_available = ($taskserv.current_version != $latest_version)
|
||||
|
||||
let status = if $update_available { "update_available" } else { "up_to_date" }
|
||||
let message = if $update_available { $"Update available: ($taskserv.current_version) → ($latest_version)" } else { "Up to date" }
|
||||
|
||||
{
|
||||
id: $taskserv.id
|
||||
status: $status
|
||||
current: $taskserv.current_version
|
||||
latest: $latest_version
|
||||
update_available: $update_available
|
||||
message: $message
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# Display results
|
||||
for result in $update_results {
|
||||
let icon = match $result.status {
|
||||
"update_available" => "🆙"
|
||||
"up_to_date" => "✅"
|
||||
"pinned" => "📌"
|
||||
"no_version" => "⚠️"
|
||||
"no_source" => "❓"
|
||||
_ => "❔"
|
||||
}
|
||||
|
||||
print $" ($icon) ($result.id): ($result.message)"
|
||||
}
|
||||
|
||||
print ""
|
||||
let total_count = ($update_results | length)
|
||||
let updates_available = ($update_results | where update_available | length)
|
||||
let pinned_count = ($update_results | where status == "pinned" | length)
|
||||
let no_version_count = ($update_results | where status == "no_version" | length)
|
||||
|
||||
print $"📊 Summary: ($total_count) taskservs checked"
|
||||
print $" - ($updates_available) updates available"
|
||||
print $" - ($pinned_count) pinned"
|
||||
print $" - ($no_version_count) without version definitions"
|
||||
|
||||
if $updates_available > 0 {
|
||||
print ""
|
||||
print "💡 To update a taskserv: provisioning taskserv update <name> <version>"
|
||||
}
|
||||
}
|
||||
70
core/nulib/main_provisioning/versions.nu
Normal file
70
core/nulib/main_provisioning/versions.nu
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Version Management Commands
|
||||
# Manages versions with progressive cache hierarchy
|
||||
|
||||
use ../lib_provisioning/cache/cache_manager.nu *
|
||||
use ../lib_provisioning/cache/grace_checker.nu *
|
||||
use ../lib_provisioning/cache/version_loader.nu *
|
||||
use ../lib_provisioning/cache/batch_updater.nu *
|
||||
|
||||
# Get version for a specific component
|
||||
export def "version get" [
|
||||
component: string # Component name (e.g., kubernetes, containerd)
|
||||
]: nothing -> string {
|
||||
get-cached-version $component
|
||||
}
|
||||
|
||||
# Show cache status and statistics
|
||||
export def "version status" []: nothing -> nothing {
|
||||
show-cache-status
|
||||
}
|
||||
|
||||
# Initialize the cache system
|
||||
export def "version init" []: nothing -> nothing {
|
||||
print "🚀 Initializing version cache system..."
|
||||
init-cache-system
|
||||
print "✅ Cache system initialized"
|
||||
}
|
||||
|
||||
# Clear all cached versions
|
||||
export def "version clear" []: nothing -> nothing {
|
||||
print "🧹 Clearing version cache..."
|
||||
clear-cache-system
|
||||
print "✅ Cache cleared"
|
||||
}
|
||||
|
||||
# Update all cached versions in batches
|
||||
export def "version update-all" []: nothing -> nothing {
|
||||
print "🔄 Updating all cached versions..."
|
||||
batch-update-all
|
||||
print "✅ Cache updated"
|
||||
}
|
||||
|
||||
# Invalidate a specific component's cache entry
|
||||
export def "version invalidate" [
|
||||
component: string # Component to invalidate
|
||||
]: nothing -> nothing {
|
||||
invalidate-cache-entry $component "infra"
|
||||
invalidate-cache-entry $component "provisioning"
|
||||
print $"✅ Invalidated cache for ($component)"
|
||||
}
|
||||
|
||||
# List all available components
|
||||
export def "version list" []: nothing -> list<string> {
|
||||
get-all-components
|
||||
}
|
||||
|
||||
# Sync cache from source (force refresh)
|
||||
export def "version sync" [
|
||||
component?: string # Optional specific component
|
||||
]: nothing -> nothing {
|
||||
if ($component | is-not-empty) {
|
||||
invalidate-cache-entry $component "infra"
|
||||
invalidate-cache-entry $component "provisioning"
|
||||
let version = (get-cached-version $component)
|
||||
print $"🔄 Synced ($component): ($version)"
|
||||
} else {
|
||||
version clear
|
||||
version update-all
|
||||
print "🔄 Synced all versions"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue