481 lines
14 KiB
Plaintext
481 lines
14 KiB
Plaintext
# KCL Packaging Library
|
|
# Functions for packaging and distributing KCL modules
|
|
# Author: JesusPerezLorenzo
|
|
# Date: 2025-09-29
|
|
|
|
use config/accessor.nu *
|
|
use utils *
|
|
|
|
# Package core provisioning KCL schemas
|
|
export def "pack-core" [
|
|
--output: string = "" # Output directory (from config if not specified)
|
|
--version: string = "" # Version override
|
|
] {
|
|
_print "📦 Packaging core provisioning schemas..."
|
|
|
|
# Get config
|
|
let dist_config = (get-distribution-config)
|
|
let kcl_config = (get-kcl-config)
|
|
|
|
# Get pack path from config or use provided output
|
|
let pack_path = if ($output | is-empty) {
|
|
$dist_config.pack_path
|
|
} else {
|
|
$output
|
|
}
|
|
|
|
# Get core module path from config
|
|
let base_path = ($env.PROVISIONING_CONFIG? | default ($env.PROVISIONING? | default ""))
|
|
if ($base_path | is-empty) {
|
|
error make {msg: "PROVISIONING_CONFIG or PROVISIONING environment variable must be set"}
|
|
}
|
|
let core_module = ($kcl_config.core_module | str replace --all "{{paths.base}}" $base_path)
|
|
let core_path = $core_module
|
|
|
|
# Get version from config or use provided
|
|
let core_version = if ($version | is-empty) {
|
|
$kcl_config.core_version
|
|
} else {
|
|
$version
|
|
}
|
|
|
|
# Ensure pack_path exists and get absolute path
|
|
mkdir $pack_path
|
|
let abs_pack_path = ($pack_path | path expand)
|
|
|
|
# Change to the KCL module directory to run packaging from inside
|
|
cd $core_path
|
|
|
|
# Check if kcl mod pkg is supported
|
|
let help_result = (^kcl mod --help | complete)
|
|
let has_pkg = ($help_result.stdout | str contains "pkg")
|
|
|
|
if not $has_pkg {
|
|
_print $" ⚠️ KCL does not support 'kcl mod pkg'"
|
|
_print $" 💡 Please upgrade to KCL 0.11.3+ for packaging support"
|
|
error make {msg: "KCL packaging not supported in this version"}
|
|
}
|
|
|
|
# Run kcl mod pkg from inside the module directory with --target
|
|
_print $" Running: kcl mod pkg --target ($abs_pack_path)"
|
|
let result = (^kcl mod pkg --target $abs_pack_path | complete)
|
|
|
|
if $result.exit_code != 0 {
|
|
error make {msg: $"Failed to package core: ($result.stderr)"}
|
|
}
|
|
|
|
_print $" ✓ KCL packaging completed"
|
|
|
|
# Find the generated package in the target directory (kcl creates .tar files)
|
|
cd $abs_pack_path
|
|
let package_files = (glob *.tar)
|
|
|
|
if ($package_files | is-empty) {
|
|
_print $" ⚠️ No .tar file created in ($abs_pack_path)"
|
|
_print $" 💡 Check if kcl.mod is properly configured"
|
|
error make {msg: "KCL packaging did not create output file"}
|
|
}
|
|
|
|
let package_file = ($package_files | first)
|
|
_print $" ✓ Package: ($package_file)"
|
|
|
|
# Generate metadata
|
|
generate-package-metadata $pack_path "provisioning_core" $core_version $package_file
|
|
|
|
return $package_file
|
|
}
|
|
|
|
# Package a provider module
|
|
export def "pack-provider" [
|
|
provider: string, # Provider name
|
|
--output: string = "" # Output directory
|
|
--version: string = "" # Version override
|
|
] {
|
|
_print $"📦 Packaging provider: ($provider)..."
|
|
|
|
# Get config
|
|
let dist_config = (get-distribution-config)
|
|
let pack_path = if ($output | is-empty) {
|
|
$dist_config.pack_path
|
|
} else {
|
|
$output
|
|
}
|
|
|
|
# Get provider path from config
|
|
let config = (get-config)
|
|
let providers_base = ($config | get paths.providers)
|
|
let provider_path = ($providers_base | path join $provider "kcl")
|
|
|
|
if not ($provider_path | path exists) {
|
|
error make {msg: $"Provider not found: ($provider) at ($provider_path)"}
|
|
}
|
|
|
|
# Ensure pack_path exists and get absolute path
|
|
mkdir $pack_path
|
|
let abs_pack_path = ($pack_path | path expand)
|
|
|
|
# Change to the provider KCL directory to run packaging from inside
|
|
cd $provider_path
|
|
|
|
# Run kcl mod pkg with target directory
|
|
_print $" Running: kcl mod pkg --target ($abs_pack_path)"
|
|
let result = (^kcl mod pkg --target $abs_pack_path | complete)
|
|
|
|
if $result.exit_code != 0 {
|
|
error make {msg: $"Failed to package provider: ($result.stderr)"}
|
|
}
|
|
|
|
_print $" ✓ Provider ($provider) packaged"
|
|
|
|
# Find the generated package in the target directory
|
|
cd $abs_pack_path
|
|
let package_files = (glob *.tar)
|
|
|
|
if ($package_files | is-empty) {
|
|
error make {msg: "No package file found"}
|
|
}
|
|
|
|
let package_file = ($package_files | first)
|
|
_print $" ✓ Package: ($package_file)"
|
|
|
|
# Read version from kcl.mod if not provided
|
|
let pkg_version = if ($version | is-empty) {
|
|
let kcl_mod = ($provider_path | path join "kcl.mod")
|
|
if ($kcl_mod | path exists) {
|
|
parse-kcl-version $kcl_mod
|
|
} else {
|
|
"0.0.1"
|
|
}
|
|
} else {
|
|
$version
|
|
}
|
|
|
|
# Generate metadata
|
|
generate-package-metadata $pack_path $"($provider)_prov" $pkg_version $package_file
|
|
|
|
return $package_file
|
|
}
|
|
|
|
# Package all discovered providers
|
|
export def "pack-all-providers" [
|
|
--output: string = "" # Output directory
|
|
] {
|
|
use kcl_module_loader.nu *
|
|
|
|
let dist_config = (get-distribution-config)
|
|
let pack_path = if ($output | is-empty) {
|
|
$dist_config.pack_path
|
|
} else {
|
|
$output
|
|
}
|
|
|
|
_print "📦 Packaging all providers..."
|
|
|
|
let providers = (discover-kcl-modules "providers")
|
|
|
|
mut packaged = []
|
|
|
|
for provider in $providers {
|
|
# Use error handling without complete since pack-provider is internal
|
|
let pack_result = (do -i {
|
|
pack-provider $provider.name --output $pack_path
|
|
})
|
|
|
|
if ($pack_result | is-not-empty) and not ($pack_result | describe | str contains "error") {
|
|
$packaged = ($packaged | append {name: $provider.name, path: $pack_result, status: "success"})
|
|
} else {
|
|
_print $" ❌ Failed to package ($provider.name)"
|
|
$packaged = ($packaged | append {name: $provider.name, path: "", status: "failed"})
|
|
}
|
|
}
|
|
|
|
_print ""
|
|
_print $"✅ Packaged ($packaged | where status == success | length)/($packaged | length) providers"
|
|
|
|
return $packaged
|
|
}
|
|
|
|
# Generate package metadata
|
|
def "generate-package-metadata" [
|
|
pack_path: string,
|
|
module_name: string,
|
|
version: string,
|
|
package_file: string
|
|
] {
|
|
let dist_config = (get-distribution-config)
|
|
let registry_path = $dist_config.registry_path
|
|
mkdir $registry_path
|
|
|
|
let metadata_file = ($registry_path | path join $"($module_name).json")
|
|
|
|
let dist_metadata = ($dist_config | get metadata)
|
|
|
|
let metadata = {
|
|
name: $module_name
|
|
version: $version
|
|
package_file: $package_file
|
|
created: (date now | format date "%Y-%m-%d %H:%M:%S")
|
|
maintainer: $dist_metadata.maintainer
|
|
repository: $dist_metadata.repository
|
|
license: $dist_metadata.license
|
|
homepage: $dist_metadata.homepage
|
|
}
|
|
|
|
$metadata | to json | save -f $metadata_file
|
|
|
|
_print $" ✓ Metadata: ($metadata_file)"
|
|
}
|
|
|
|
# Parse version from kcl.mod
|
|
def "parse-kcl-version" [
|
|
kcl_mod_path: string
|
|
]: nothing -> string {
|
|
let content = (open $kcl_mod_path)
|
|
let lines = ($content | lines)
|
|
|
|
for line in $lines {
|
|
if ($line | str starts-with "version") {
|
|
let version = ($line | parse 'version = "{version}"' | get version.0? | default "0.0.1")
|
|
return $version
|
|
}
|
|
}
|
|
|
|
return "0.0.1"
|
|
}
|
|
|
|
# List packaged modules
|
|
export def "list-packages" [
|
|
--format: string = "table" # Output format
|
|
] {
|
|
let dist_config = (get-distribution-config)
|
|
let pack_path = $dist_config.pack_path
|
|
|
|
if not ($pack_path | path exists) {
|
|
_print "No packages found"
|
|
return []
|
|
}
|
|
|
|
# Look for .tar files
|
|
cd $pack_path
|
|
let tar_files = (glob *.tar)
|
|
let packages = $tar_files | each {|file|
|
|
let stats = (ls $file | first)
|
|
{
|
|
name: ($file | path basename | str replace ".tar" "")
|
|
path: $file
|
|
size: $stats.size
|
|
modified: $stats.modified
|
|
}
|
|
}
|
|
|
|
match $format {
|
|
"json" => ($packages | to json)
|
|
"yaml" => ($packages | to yaml)
|
|
_ => ($packages | table)
|
|
}
|
|
}
|
|
|
|
# Clean old packages
|
|
export def "clean-packages" [
|
|
--keep-latest: int = 3 # Number of latest versions to keep
|
|
--dry-run # Show what would be deleted
|
|
] {
|
|
let dist_config = (get-distribution-config)
|
|
let pack_path = $dist_config.pack_path
|
|
|
|
if not ($pack_path | path exists) {
|
|
_print "No packages directory found"
|
|
return
|
|
}
|
|
|
|
_print $"🗑️ Cleaning packages \(keeping latest ($keep_latest)\)..."
|
|
|
|
cd $pack_path
|
|
let tar_files = (glob *.tar)
|
|
if ($tar_files | is-empty) {
|
|
_print " No packages to clean"
|
|
return
|
|
}
|
|
|
|
let packages = ($tar_files | each {|file|
|
|
let stats = (ls $file | first)
|
|
{
|
|
name: $file
|
|
modified: $stats.modified
|
|
}
|
|
} | sort-by modified --reverse)
|
|
|
|
let to_delete = ($packages | skip $keep_latest)
|
|
|
|
if ($to_delete | is-empty) {
|
|
_print " Nothing to clean"
|
|
return
|
|
}
|
|
|
|
# Get registry path for metadata cleanup
|
|
let dist_config = (get-distribution-config)
|
|
let registry_path = $dist_config.registry_path
|
|
|
|
for pkg in $to_delete {
|
|
let pkg_basename = ($pkg.name | path basename)
|
|
|
|
# Extract package name without version (e.g., "aws_prov_0.0.1.tar" -> "aws_prov")
|
|
let pkg_name = ($pkg_basename | str replace -r '_\d+\.\d+\.\d+\.tar$' '')
|
|
let metadata_file = ($registry_path | path join $"($pkg_name).json")
|
|
|
|
if $dry_run {
|
|
_print $" [DRY RUN] Would delete package: ($pkg_basename)"
|
|
if ($metadata_file | path exists) {
|
|
_print $" [DRY RUN] Would delete metadata: ($metadata_file | path basename)"
|
|
}
|
|
} else {
|
|
rm $pkg.name
|
|
_print $" ✓ Deleted package: ($pkg_basename)"
|
|
|
|
# Also delete corresponding metadata if it exists
|
|
if ($metadata_file | path exists) {
|
|
rm $metadata_file
|
|
_print $" ✓ Deleted metadata: ($metadata_file | path basename)"
|
|
}
|
|
}
|
|
}
|
|
|
|
let deleted_count = ($to_delete | length)
|
|
_print $"✅ Cleaned ($deleted_count) old packages"
|
|
}
|
|
|
|
# Remove specific package and its metadata
|
|
export def "remove-package" [
|
|
package_name: string, # Package name (e.g., "aws_prov", "upcloud_prov", "provisioning_core")
|
|
--force = false # Skip confirmation prompt
|
|
] {
|
|
_print $"🗑️ Removing package: ($package_name)"
|
|
|
|
let dist_config = (get-distribution-config)
|
|
let pack_path = $dist_config.pack_path
|
|
let registry_path = $dist_config.registry_path
|
|
|
|
# Find all versions of this package
|
|
cd $pack_path
|
|
let package_pattern = $"($package_name)*.tar"
|
|
let matching_packages = (glob $package_pattern)
|
|
|
|
if ($matching_packages | is-empty) {
|
|
_print $" ❌ No packages found matching: ($package_name)"
|
|
return
|
|
}
|
|
|
|
# Show what will be deleted
|
|
_print ""
|
|
_print " Packages to remove:"
|
|
for pkg in $matching_packages {
|
|
_print $" • ($pkg | path basename)"
|
|
}
|
|
|
|
# Check for metadata
|
|
let metadata_file = ($registry_path | path join $"($package_name).json")
|
|
if ($metadata_file | path exists) {
|
|
_print $" • ($metadata_file | path basename) (metadata)"
|
|
}
|
|
|
|
# Confirmation unless forced
|
|
if not $force {
|
|
_print ""
|
|
let response = (input $"⚠️ Remove ($matching_packages | length) package\(s\) and metadata? \(y/N\): ")
|
|
|
|
if ($response | str downcase) != "y" {
|
|
_print "❌ Cancelled"
|
|
return
|
|
}
|
|
}
|
|
|
|
# Delete packages
|
|
_print ""
|
|
for pkg in $matching_packages {
|
|
rm $pkg
|
|
_print $" ✓ Deleted: ($pkg | path basename)"
|
|
}
|
|
|
|
# Delete metadata
|
|
if ($metadata_file | path exists) {
|
|
rm $metadata_file
|
|
_print $" ✓ Deleted: ($metadata_file | path basename)"
|
|
}
|
|
|
|
_print ""
|
|
_print $"✅ Removed package: ($package_name)"
|
|
}
|
|
|
|
# Clean ALL packages and metadata
|
|
export def "clean-all-packages" [
|
|
--dry-run = false, # Show what would be deleted
|
|
--force = false # Skip confirmation prompt
|
|
] {
|
|
_print "🗑️ Cleaning ALL packages..."
|
|
|
|
let dist_config = (get-distribution-config)
|
|
let pack_path = $dist_config.pack_path
|
|
let registry_path = $dist_config.registry_path
|
|
|
|
if not ($pack_path | path exists) {
|
|
_print " No packages directory found"
|
|
return
|
|
}
|
|
|
|
# Get all packages
|
|
cd $pack_path
|
|
let all_packages = (glob *.tar)
|
|
|
|
if ($all_packages | is-empty) {
|
|
_print " No packages to clean"
|
|
return
|
|
}
|
|
|
|
# Get all metadata files
|
|
cd $registry_path
|
|
let all_metadata = (glob *.json)
|
|
|
|
# Show what will be deleted
|
|
_print ""
|
|
_print $" Found ($all_packages | length) package\(s\) and ($all_metadata | length) metadata file\(s\)"
|
|
|
|
if not $dry_run and not $force {
|
|
_print ""
|
|
let response = (input $"⚠️ Delete ALL packages and metadata? \(y/N\): ")
|
|
|
|
if ($response | str downcase) != "y" {
|
|
_print "❌ Cancelled"
|
|
return
|
|
}
|
|
}
|
|
|
|
# Delete packages
|
|
_print ""
|
|
for pkg in $all_packages {
|
|
if $dry_run {
|
|
_print $" [DRY RUN] Would delete: ($pkg | path basename)"
|
|
} else {
|
|
cd $pack_path
|
|
rm $pkg
|
|
_print $" ✓ Deleted: ($pkg | path basename)"
|
|
}
|
|
}
|
|
|
|
# Delete metadata
|
|
for meta in $all_metadata {
|
|
if $dry_run {
|
|
_print $" [DRY RUN] Would delete: ($meta | path basename)"
|
|
} else {
|
|
cd $registry_path
|
|
rm $meta
|
|
_print $" ✓ Deleted: ($meta | path basename)"
|
|
}
|
|
}
|
|
|
|
_print ""
|
|
if $dry_run {
|
|
_print $"[DRY RUN] Would clean ($all_packages | length) packages and ($all_metadata | length) metadata files"
|
|
} else {
|
|
_print $"✅ Cleaned ($all_packages | length) packages and ($all_metadata | length) metadata files"
|
|
}
|
|
} |