391 lines
13 KiB
Plaintext
Executable File
391 lines
13 KiB
Plaintext
Executable File
#!/usr/bin/env nu
|
|
|
|
# Workspace Migration Tool
|
|
# Migrates existing workspaces to new KCL package and module loader structure
|
|
|
|
use ../core/nulib/lib_provisioning/config/accessor.nu *
|
|
|
|
# Main migration command
|
|
def main [workspace_path: string] {
|
|
print $"Migrating workspace: ($workspace_path)"
|
|
|
|
# Validate workspace exists
|
|
if not ($workspace_path | path exists) {
|
|
error make { msg: $"Workspace not found: ($workspace_path)" }
|
|
}
|
|
|
|
# Analyze current workspace
|
|
let analysis = analyze-workspace $workspace_path
|
|
|
|
print $"📊 Workspace Analysis:"
|
|
print $" Type: ($analysis.type)"
|
|
print $" KCL files: ($analysis.kcl_files | length)"
|
|
print $" Has kcl.mod: ($analysis.has_kcl_mod)"
|
|
print $" Extension references: ($analysis.extension_refs | length)"
|
|
|
|
# Perform migration steps
|
|
let migration_result = migrate-workspace $workspace_path $analysis
|
|
|
|
print $"✅ Migration completed"
|
|
print $"📁 New structure created"
|
|
print $"📄 Configuration files updated"
|
|
print $"🔄 Import paths converted"
|
|
|
|
if not ($migration_result.warnings | is-empty) {
|
|
print ""
|
|
print "⚠️ Warnings:"
|
|
for $warning in $migration_result.warnings {
|
|
print $" - ($warning)"
|
|
}
|
|
}
|
|
|
|
print ""
|
|
print "Next steps:"
|
|
print " 1. Review updated kcl.mod file"
|
|
print " 2. Load required modules: module-loader load taskservs . [modules...]"
|
|
print " 3. Test configuration: kcl run servers.k"
|
|
print " 4. Deploy: provisioning server create --infra . --check"
|
|
}
|
|
|
|
# Analyze existing workspace structure
|
|
def analyze-workspace [workspace_path: string]: nothing -> record {
|
|
let workspace_abs = ($workspace_path | path expand)
|
|
|
|
# Find KCL files
|
|
let kcl_files = try {
|
|
glob ($workspace_abs | path join "**/*.k") | where { |path| $path | path exists }
|
|
} catch { [] }
|
|
|
|
# Check for kcl.mod
|
|
let kcl_mod_path = ($workspace_abs | path join "kcl.mod")
|
|
let has_kcl_mod = ($kcl_mod_path | path exists)
|
|
|
|
# Find extension references in KCL files
|
|
let extension_refs = ($kcl_files | each { |file|
|
|
let content = try { open $file } catch { "" }
|
|
# Look for relative imports pointing to extensions
|
|
let imports = ($content | lines | where { |line| ($line | str contains "import") and ($line | str contains "../") })
|
|
$imports | each { |import| { file: $file, import: $import } }
|
|
} | flatten)
|
|
|
|
# Detect workspace type
|
|
let workspace_type = if ($extension_refs | where { |ref| $ref.import | str contains "taskservs" } | length) > 0 {
|
|
"legacy-with-taskservs"
|
|
} else if ($kcl_files | length) > 0 {
|
|
"kcl-workspace"
|
|
} else {
|
|
"basic"
|
|
}
|
|
|
|
{
|
|
path: $workspace_abs,
|
|
type: $workspace_type,
|
|
kcl_files: $kcl_files,
|
|
has_kcl_mod: $has_kcl_mod,
|
|
extension_refs: $extension_refs,
|
|
needs_migration: ((not $has_kcl_mod) or (($extension_refs | length) > 0))
|
|
}
|
|
}
|
|
|
|
# Perform workspace migration
|
|
def migrate-workspace [workspace_path: string, analysis: record]: nothing -> record {
|
|
let workspace_abs = ($workspace_path | path expand)
|
|
let warnings = []
|
|
|
|
# Step 1: Create new directory structure
|
|
print "🔧 Creating new directory structure..."
|
|
mkdir ($workspace_abs | path join ".taskservs")
|
|
mkdir ($workspace_abs | path join ".providers")
|
|
mkdir ($workspace_abs | path join ".clusters")
|
|
mkdir ($workspace_abs | path join ".manifest")
|
|
|
|
# Ensure other standard directories exist
|
|
mkdir ($workspace_abs | path join "data")
|
|
mkdir ($workspace_abs | path join "tmp")
|
|
mkdir ($workspace_abs | path join "resources")
|
|
mkdir ($workspace_abs | path join "clusters")
|
|
|
|
# Step 2: Create or update kcl.mod
|
|
print "🔧 Creating/updating kcl.mod..."
|
|
let kcl_mod_content = if $analysis.has_kcl_mod {
|
|
# Update existing kcl.mod
|
|
let existing = (open ($workspace_abs | path join "kcl.mod"))
|
|
update-kcl-mod $existing
|
|
} else {
|
|
# Create new kcl.mod
|
|
create-new-kcl-mod ($workspace_path | path basename)
|
|
}
|
|
$kcl_mod_content | save -f ($workspace_abs | path join "kcl.mod")
|
|
|
|
# Step 3: Convert import paths in KCL files
|
|
print "🔧 Converting import paths..."
|
|
let conversion_results = ($analysis.kcl_files | each { |file|
|
|
convert-imports-in-file $file
|
|
})
|
|
|
|
# Step 4: Create empty manifest files
|
|
print "🔧 Creating manifest files..."
|
|
let empty_manifest = {
|
|
last_updated: (date now | format date "%Y-%m-%d %H:%M:%S"),
|
|
workspace: $workspace_abs
|
|
}
|
|
|
|
($empty_manifest | merge { loaded_taskservs: [] }) | to yaml | save -f ($workspace_abs | path join ".manifest" | path join "taskservs.yaml")
|
|
($empty_manifest | merge { loaded_providers: [] }) | to yaml | save -f ($workspace_abs | path join ".manifest" | path join "providers.yaml")
|
|
($empty_manifest | merge { loaded_clusters: [] }) | to yaml | save -f ($workspace_abs | path join ".manifest" | path join "clusters.yaml")
|
|
|
|
# Step 5: Create .gitignore if it doesn't exist
|
|
let gitignore_path = ($workspace_abs | path join ".gitignore")
|
|
if not ($gitignore_path | path exists) {
|
|
print "🔧 Creating .gitignore..."
|
|
let gitignore_content = "# Workspace runtime data
|
|
.manifest/
|
|
data/
|
|
tmp/
|
|
*.log
|
|
|
|
# Module directories (managed by module-loader)
|
|
.taskservs/
|
|
.providers/
|
|
.clusters/
|
|
|
|
# Generated files
|
|
taskservs.k
|
|
providers.k
|
|
clusters.k
|
|
|
|
# Secrets and sensitive data
|
|
*.age
|
|
*.enc
|
|
*.key
|
|
"
|
|
$gitignore_content | save $gitignore_path
|
|
}
|
|
|
|
# Step 6: Create migration summary
|
|
let migration_info = {
|
|
migrated_at: (date now | format date "%Y-%m-%d %H:%M:%S"),
|
|
original_type: $analysis.type,
|
|
files_converted: ($conversion_results | length),
|
|
extension_refs_converted: ($analysis.extension_refs | length)
|
|
}
|
|
|
|
$migration_info | to yaml | save ($workspace_abs | path join ".migration-info.yaml")
|
|
|
|
{
|
|
success: true,
|
|
workspace: $workspace_abs,
|
|
files_converted: ($conversion_results | length),
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Update existing kcl.mod to add provisioning dependency
|
|
def update-kcl-mod [existing_content: string]: nothing -> string {
|
|
let lines = ($existing_content | lines)
|
|
|
|
# Check if provisioning dependency already exists
|
|
let has_provisioning_dep = ($lines | any { |line| $line | str contains "provisioning" })
|
|
|
|
if $has_provisioning_dep {
|
|
return $existing_content
|
|
}
|
|
|
|
# Find [dependencies] section or add it
|
|
let deps_line_idx = ($lines | enumerate | where { |item| $item.item | str contains "[dependencies]" } | get index | first)
|
|
|
|
if ($deps_line_idx | is-empty) {
|
|
# Add dependencies section
|
|
$existing_content + "\n\n[dependencies]\nprovisioning = { path = \"~/.kcl/packages/provisioning\", version = \"0.0.1\" }\n"
|
|
} else {
|
|
# Insert after dependencies line
|
|
let before = ($lines | first ($deps_line_idx + 1))
|
|
let after = ($lines | skip ($deps_line_idx + 1))
|
|
($before | append ["provisioning = { path = \"~/.kcl/packages/provisioning\", version = \"0.0.1\" }"] | append $after | str join "\n")
|
|
}
|
|
}
|
|
|
|
# Create new kcl.mod file
|
|
def create-new-kcl-mod [workspace_name: string]: nothing -> string {
|
|
$"[package]
|
|
name = \"($workspace_name)\"
|
|
edition = \"v0.11.3\"
|
|
version = \"0.0.1\"
|
|
|
|
[dependencies]
|
|
provisioning = { path = \"~/.kcl/packages/provisioning\", version = \"0.0.1\" }
|
|
"
|
|
}
|
|
|
|
# Convert import paths in a KCL file
|
|
def convert-imports-in-file [file_path: string]: nothing -> record {
|
|
let content = try { open $file_path } catch { return { file: $file_path, status: "error", message: "Could not read file" } }
|
|
|
|
# Define import conversion patterns
|
|
let conversions = [
|
|
# Convert relative imports to package imports
|
|
{ pattern: "import ../../../kcl/", replacement: "import provisioning." }
|
|
{ pattern: "import ../../kcl/", replacement: "import provisioning." }
|
|
{ pattern: "import ../kcl/", replacement: "import provisioning." }
|
|
|
|
# Convert extension imports (these will need to be loaded via module-loader)
|
|
{ pattern: "import ../../../extensions/taskservs/", replacement: "# import .taskservs." }
|
|
{ pattern: "import ../../extensions/taskservs/", replacement: "# import .taskservs." }
|
|
{ pattern: "import ../extensions/taskservs/", replacement: "# import .taskservs." }
|
|
|
|
{ pattern: "import ../../../extensions/providers/", replacement: "# import .providers." }
|
|
{ pattern: "import ../../extensions/providers/", replacement: "# import .providers." }
|
|
{ pattern: "import ../extensions/providers/", replacement: "# import .providers." }
|
|
|
|
{ pattern: "import ../../../extensions/clusters/", replacement: "# import .clusters." }
|
|
{ pattern: "import ../../extensions/clusters/", replacement: "# import .clusters." }
|
|
{ pattern: "import ../extensions/clusters/", replacement: "# import .clusters." }
|
|
]
|
|
|
|
# Apply conversions
|
|
let updated_content = ($conversions | reduce -f $content { |acc, conv|
|
|
$acc | str replace -a $conv.pattern $conv.replacement
|
|
})
|
|
|
|
# Check if any changes were made
|
|
if $content != $updated_content {
|
|
# Backup original file
|
|
let backup_path = ($file_path + ".bak")
|
|
$content | save $backup_path
|
|
|
|
# Save updated content
|
|
$updated_content | save -f $file_path
|
|
|
|
{
|
|
file: $file_path,
|
|
status: "converted",
|
|
backup: $backup_path,
|
|
changes: true
|
|
}
|
|
} else {
|
|
{
|
|
file: $file_path,
|
|
status: "no-changes",
|
|
changes: false
|
|
}
|
|
}
|
|
}
|
|
|
|
# Dry run migration (analyze what would be changed)
|
|
export def "main dry-run" [workspace_path: string]: nothing -> record {
|
|
print $"Analyzing workspace for migration: ($workspace_path)"
|
|
|
|
let analysis = analyze-workspace $workspace_path
|
|
|
|
print $"📊 Migration Analysis Report:"
|
|
print $" Workspace: ($analysis.path)"
|
|
print $" Type: ($analysis.type)"
|
|
print $" Needs migration: ($analysis.needs_migration)"
|
|
print ""
|
|
print $" KCL files found: ($analysis.kcl_files | length)"
|
|
for $file in $analysis.kcl_files {
|
|
print $" - ($file | str replace ($analysis.path + '/') '')"
|
|
}
|
|
print ""
|
|
print $" Extension references: ($analysis.extension_refs | length)"
|
|
for $ref in $analysis.extension_refs {
|
|
let file_rel = ($ref.file | str replace ($analysis.path + '/') '')
|
|
print $" - ($file_rel): ($ref.import | str trim)"
|
|
}
|
|
|
|
if $analysis.needs_migration {
|
|
print ""
|
|
print "🔧 Migration steps that would be performed:"
|
|
print " 1. Create .taskservs/, .providers/, .clusters/, .manifest/ directories"
|
|
print " 2. Create/update kcl.mod with provisioning dependency"
|
|
print " 3. Convert import paths from relative to package-based"
|
|
print " 4. Comment out extension imports (load via module-loader)"
|
|
print " 5. Create manifest files for module tracking"
|
|
print " 6. Create .gitignore if missing"
|
|
} else {
|
|
print ""
|
|
print "✅ Workspace appears to already be migrated or needs no migration"
|
|
}
|
|
|
|
$analysis
|
|
}
|
|
|
|
# Rollback migration (restore from backups)
|
|
export def "main rollback" [workspace_path: string]: nothing -> nothing {
|
|
print $"Rolling back migration for: ($workspace_path)"
|
|
|
|
let workspace_abs = ($workspace_path | path expand)
|
|
|
|
# Find backup files
|
|
let backup_files = try {
|
|
glob ($workspace_abs | path join "**/*.bak")
|
|
} catch { [] }
|
|
|
|
if ($backup_files | is-empty) {
|
|
print "⚠️ No backup files found. Cannot rollback."
|
|
return
|
|
}
|
|
|
|
print $"Found ($backup_files | length) backup files"
|
|
|
|
# Restore each backup
|
|
for $backup in $backup_files {
|
|
let original = ($backup | str replace ".bak" "")
|
|
print $"Restoring: ($original | str replace ($workspace_abs + '/') '')"
|
|
|
|
# Copy backup back to original
|
|
cp $backup $original
|
|
|
|
# Remove backup
|
|
rm $backup
|
|
}
|
|
|
|
# Remove migration artifacts
|
|
let migration_artifacts = [
|
|
".taskservs"
|
|
".providers"
|
|
".clusters"
|
|
".manifest"
|
|
".migration-info.yaml"
|
|
]
|
|
|
|
for $artifact in $migration_artifacts {
|
|
let artifact_path = ($workspace_abs | path join $artifact)
|
|
if ($artifact_path | path exists) {
|
|
rm -rf $artifact_path
|
|
}
|
|
}
|
|
|
|
print "✅ Migration rolled back successfully"
|
|
print " Original files restored from backups"
|
|
print " Migration artifacts removed"
|
|
}
|
|
|
|
# Show migration status
|
|
export def "main status" [workspace_path: string]: nothing -> record {
|
|
let workspace_abs = ($workspace_path | path expand)
|
|
|
|
let has_new_structure = [".taskservs", ".providers", ".clusters", ".manifest"] | all { |dir|
|
|
($workspace_abs | path join $dir) | path exists
|
|
}
|
|
|
|
let migration_info_path = ($workspace_abs | path join ".migration-info.yaml")
|
|
let migration_info = if ($migration_info_path | path exists) {
|
|
open $migration_info_path
|
|
} else {
|
|
null
|
|
}
|
|
|
|
let backup_files = try {
|
|
glob ($workspace_abs | path join "**/*.bak") | length
|
|
} catch { 0 }
|
|
|
|
{
|
|
workspace: $workspace_abs,
|
|
has_new_structure: $has_new_structure,
|
|
migration_info: $migration_info,
|
|
backup_files: $backup_files,
|
|
is_migrated: ($has_new_structure and ($migration_info != null))
|
|
}
|
|
}
|