provisioning/workspace/tools/migrate-infra.nu
2025-10-07 11:12:02 +01:00

434 lines
15 KiB
Plaintext
Executable File

#!/usr/bin/env nu
# Infrastructure Migration Tool
# Helps migrate existing infrastructures to use the new layered template system
# Extract infrastructure patterns to templates
export def "migrate extract" [
infra_name: string, # Source infrastructure (e.g., "wuji")
--dry-run = false # Show what would be done without doing it
] {
print $"🔄 Extracting patterns from ($infra_name) infrastructure"
let infra_path = $"../../../workspace/infra/($infra_name)"
let templates_path = "../templates"
if not ($infra_path | path exists) {
error make {msg: $"Infrastructure ($infra_name) not found at ($infra_path)"}
}
# Extract taskservs
if ($"($infra_path)/taskservs" | path exists) {
print " 📦 Extracting taskserv patterns..."
extract_taskservs $infra_path $templates_path $dry_run
}
# Extract provider defaults
if ($"($infra_path)/defs" | path exists) {
print " 📦 Extracting provider patterns..."
extract_providers $infra_path $templates_path $dry_run
}
# Extract server patterns
if ($"($infra_path)/defs/servers.k" | path exists) {
print " 📦 Extracting server patterns..."
extract_servers $infra_path $templates_path $dry_run
}
if not $dry_run {
print $"✅ Extraction completed for ($infra_name)"
print $"📝 Templates created in ($templates_path)"
print $"Next step: Run 'migrate convert ($infra_name)' to convert infrastructure to use templates"
} else {
print $"✅ Dry run completed - no files were created"
}
}
# Convert infrastructure to use templates
export def "migrate convert" [
infra_name: string, # Infrastructure to convert
--backup = true, # Create backup before conversion
--dry-run = false # Show what would be done
] {
print $"🔄 Converting ($infra_name) to use template system"
let infra_path = $"../../../workspace/infra/($infra_name)"
if not ($infra_path | path exists) {
error make {msg: $"Infrastructure ($infra_name) not found"}
}
if $backup and not $dry_run {
let backup_path = $"($infra_path).backup-(date now | format date '%Y%m%d_%H%M%S')"
print $"📁 Creating backup at ($backup_path)"
cp -r $infra_path $backup_path
}
# Convert taskservs to use templates
convert_taskservs_to_templates $infra_path $infra_name $dry_run
# Convert provider configs
convert_providers_to_templates $infra_path $infra_name $dry_run
if not $dry_run {
print $"✅ Conversion completed for ($infra_name)"
print $"🧪 Test with: module-loader-enhanced layer test kubernetes --infra ($infra_name)"
} else {
print $"✅ Dry run completed - no files were modified"
}
}
# Validate template usage in infrastructure
export def "migrate validate" [
infra_name: string # Infrastructure to validate
] {
print $"🔍 Validating template usage in ($infra_name)"
let infra_path = $"../../../workspace/infra/($infra_name)"
let issues = []
if not ($infra_path | path exists) {
error make {msg: $"Infrastructure ($infra_name) not found"}
}
# Check for template imports
validate_template_imports $infra_path $infra_name
# Check layer resolution
validate_layer_resolution $infra_name
print $"✅ Validation completed for ($infra_name)"
}
# Compare configurations between infrastructures
export def "migrate diff" [
source_infra: string, # Source infrastructure
target_infra: string # Target infrastructure
] {
print $"🔍 Comparing ($source_infra) vs ($target_infra)"
let source_path = $"../../../workspace/infra/($source_infra)"
let target_path = $"../../../workspace/infra/($target_infra)"
compare_taskservs $source_path $target_path
compare_providers $source_path $target_path
print $"✅ Comparison completed"
}
# === HELPER FUNCTIONS ===
def extract_taskservs [infra_path: string, templates_path: string, dry_run: bool] {
let taskservs_dir = $"($infra_path)/taskservs"
if ($taskservs_dir | path exists) {
for file in (ls $taskservs_dir | where type == file | get name) {
let filename = ($file | path basename)
let taskserv_name = ($filename | str replace ".k" "")
# Determine template category based on taskserv name
let category = categorize_taskserv $taskserv_name
let target_dir = $"($templates_path)/taskservs/($category)"
let target_file = $"($target_dir)/($filename)"
print $" ➜ ($taskserv_name) -> ($category)/($filename)"
if not $dry_run {
mkdir $target_dir
# Read original file and convert to template
let content = open $file
let template_content = create_taskserv_template $content $taskserv_name
$template_content | save $target_file
}
}
}
}
def extract_providers [infra_path: string, templates_path: string, dry_run: bool] {
let defs_dir = $"($infra_path)/defs"
if ($defs_dir | path exists) {
for file in (ls $defs_dir | where name =~ "_defaults\.k$" | get name) {
let filename = ($file | path basename)
let provider_name = ($filename | str replace "_defaults.k" "")
let target_dir = $"($templates_path)/providers/($provider_name)"
let target_file = $"($target_dir)/defaults.k"
print $" ➜ ($provider_name) defaults -> providers/($provider_name)/defaults.k"
if not $dry_run {
mkdir $target_dir
# Read and convert to template
let content = open $file
let template_content = create_provider_template $content $provider_name
$template_content | save $target_file
}
}
}
}
def extract_servers [infra_path: string, templates_path: string, dry_run: bool] {
let servers_file = $"($infra_path)/defs/servers.k"
if ($servers_file | path exists) {
print $" ➜ Extracting server patterns"
if not $dry_run {
# Parse servers.k and create templates
create_server_templates $servers_file $templates_path
}
}
}
def convert_taskservs_to_templates [infra_path: string, infra_name: string, dry_run: bool] {
let taskservs_dir = $"($infra_path)/taskservs"
if ($taskservs_dir | path exists) {
print $" 🔧 Converting taskservs to use templates..."
for file in (ls $taskservs_dir | where type == file | get name) {
let filename = ($file | path basename)
let taskserv_name = ($filename | str replace ".k" "")
print $" ➜ Converting ($taskserv_name)"
if not $dry_run {
# Create new file that uses templates
let new_content = create_template_based_taskserv $taskserv_name $infra_name
$new_content | save $file
}
}
}
}
def convert_providers_to_templates [infra_path: string, infra_name: string, dry_run: bool] {
let defs_dir = $"($infra_path)/defs"
if ($defs_dir | path exists) {
print $" 🔧 Converting providers to use templates..."
for file in (ls $defs_dir | where name =~ "_defaults\.k$" | get name) {
let filename = ($file | path basename)
let provider_name = ($filename | str replace "_defaults.k" "")
print $" ➜ Converting ($provider_name) defaults"
if not $dry_run {
# Create new file that uses templates
let new_content = create_template_based_provider $provider_name $infra_name
$new_content | save $file
}
}
}
}
def categorize_taskserv [name: string] -> string {
match $name {
$n if ($n | str contains "kubernetes") => "kubernetes"
$n if ($n | str contains "k8s") => "kubernetes"
$n if ($n in ["rook-ceph", "mayastor"]) => "storage"
$n if ($n in ["cilium", "coredns", "proxy"]) => "networking"
$n if ($n in ["containerd", "crio", "podman", "runc", "crun", "youki"]) => "container-runtime"
_ => "misc"
}
}
def create_taskserv_template [content: string, name: string] -> string {
# Convert existing taskserv to template format
# This is a simplified version - in practice, you'd parse the KCL properly
let template_header = $"# ($name | str title-case) Template
# Extracted from infrastructure patterns
# Provides ($name) configuration with customizable defaults
import taskservs.($name).kcl.($name) as ($name)_core
import ../../../workspace/templates/lib/compose as comp
"
# Add the existing content with template wrapper
$"($template_header)($content)"
}
def create_provider_template [content: string, provider: string] -> string {
let template_header = $"# ($provider | str title-case) Provider Template
# Extracted from infrastructure patterns
# Provides ($provider) provider defaults with customizable options
import ($provider)_prov
import ../../../workspace/templates/lib/compose as comp
import ../../../workspace/templates/lib/override as ovr
"
$"($template_header)($content)"
}
def create_server_templates [servers_file: string, templates_path: string] {
# Parse servers.k and create control-plane and worker templates
# This would need more sophisticated KCL parsing
let servers_content = open $servers_file
# Create control-plane template
let cp_template = $"# Control Plane Server Template
# Extracted from server patterns
import upcloud_prov
import ../../../workspace/templates/lib/compose as comp
schema ControlPlaneServer {
hostname: str
plan: str = \"2xCPU-4GB\"
# ... other control plane specific settings
}
control_plane_server = ControlPlaneServer {}"
mkdir $"($templates_path)/servers"
$cp_template | save $"($templates_path)/servers/control-plane.k"
}
def create_template_based_taskserv [name: string, infra: string] -> string {
$"# ($name | str title-case) Configuration for ($infra)
# Uses workspace templates with infrastructure-specific overrides
import ../../../provisioning/workspace/registry/imports as reg
import ../../../provisioning/workspace/templates/lib/compose as comp
import ../../../provisioning/workspace/templates/lib/override as ovr
# Use template with infrastructure-specific overrides
_taskserv = ovr.infrastructure_overrides.taskserv_override (
reg.tpl_($name)_base.($name)_base,
\"($infra)\",
\"($name)\",
{
cluster_name: \"($infra)\"
# Add other infrastructure-specific overrides here
}
)
_taskserv"
}
def create_template_based_provider [provider: string, infra: string] -> string {
$"# ($provider | str title-case) Defaults for ($infra)
# Uses workspace templates with infrastructure-specific overrides
import ../../../provisioning/workspace/registry/imports as reg
import ../../../provisioning/workspace/templates/lib/compose as comp
import ../../../provisioning/workspace/templates/lib/override as ovr
# Use template with infrastructure-specific overrides
ovr.infrastructure_overrides.create_infra_config (
reg.tpl_($provider)_defaults.($provider)_defaults,
\"($infra)\",
\"($provider)\",
\"($infra).local\", # Update with actual domain
{
# Add infrastructure-specific overrides here
}
)"
}
def validate_template_imports [infra_path: string, infra_name: string] {
print $" 🔍 Checking template imports..."
# Check if files use template imports
let taskservs_dir = $"($infra_path)/taskservs"
if ($taskservs_dir | path exists) {
for file in (ls $taskservs_dir | where type == file | get name) {
let content = open $file
let has_template_import = ($content | str contains "workspace/templates") or ($content | str contains "workspace/registry/imports")
let filename = ($file | path basename)
if $has_template_import {
print $" ✅ ($filename) uses templates"
} else {
print $" ⚠️ ($filename) not using templates"
}
}
}
}
def validate_layer_resolution [infra_name: string] {
print $" 🔍 Validating layer resolution..."
# Test common taskservs
let common_taskservs = ["kubernetes", "cilium", "containerd"]
for taskserv in $common_taskservs {
let core_exists = ($"../../../provisioning/extensions/taskservs/($taskserv)" | path exists)
let workspace_exists = ($"../templates/taskservs/($taskserv)" | path exists)
let infra_exists = ($"../../../workspace/infra/($infra_name)/taskservs/($taskserv).k" | path exists)
let resolution = []
if $infra_exists { $resolution = ($resolution | append "infra") }
if $workspace_exists { $resolution = ($resolution | append "workspace") }
if $core_exists { $resolution = ($resolution | append "core") }
if ($resolution | length) > 0 {
print $" ✅ ($taskserv): (($resolution | str join ' -> '))"
} else {
print $" ❌ ($taskserv): not found in any layer"
}
}
}
def compare_taskservs [source_path: string, target_path: string] {
print $" 📊 Comparing taskservs..."
let source_taskservs = if ($"($source_path)/taskservs" | path exists) {
ls $"($source_path)/taskservs" | get name | each {|f| $f | path basename | str replace ".k" ""}
} else { [] }
let target_taskservs = if ($"($target_path)/taskservs" | path exists) {
ls $"($target_path)/taskservs" | get name | each {|f| $f | path basename | str replace ".k" ""}
} else { [] }
let common = $source_taskservs | filter {|t| $t in $target_taskservs}
let source_only = $source_taskservs | filter {|t| not ($t in $target_taskservs)}
let target_only = $target_taskservs | filter {|t| not ($t in $source_taskservs)}
if ($common | length) > 0 {
print $" ✅ Common: (($common | str join ', '))"
}
if ($source_only | length) > 0 {
print $" ➡️ Source only: (($source_only | str join ', '))"
}
if ($target_only | length) > 0 {
print $" ⬅️ Target only: (($target_only | str join ', '))"
}
}
def compare_providers [source_path: string, target_path: string] {
print $" 📊 Comparing providers..."
let source_providers = if ($"($source_path)/defs" | path exists) {
ls $"($source_path)/defs" | where name =~ "_defaults\.k$" | get name | each {|f| $f | path basename | str replace "_defaults.k" ""}
} else { [] }
let target_providers = if ($"($target_path)/defs" | path exists) {
ls $"($target_path)/defs" | where name =~ "_defaults\.k$" | get name | each {|f| $f | path basename | str replace "_defaults.k" ""}
} else { [] }
let common = $source_providers | filter {|p| $p in $target_providers}
let source_only = $source_providers | filter {|p| not ($p in $target_providers)}
let target_only = $target_providers | filter {|p| not ($p in $source_providers)}
if ($common | length) > 0 {
print $" ✅ Common: (($common | str join ', '))"
}
if ($source_only | length) > 0 {
print $" ➡️ Source only: (($source_only | str join ', '))"
}
if ($target_only | length) > 0 {
print $" ⬅️ Target only: (($target_only | str join ', '))"
}
}