#!/usr/bin/env nu # Cluster Loader System # Loads selected clusters into workspace or infrastructure (Layer 2 or Layer 3) use discover.nu * use ../lib_provisioning/layers/resolver.nu * # Load clusters into workspace or infrastructure export def load-clusters [ target_path: string, clusters: list, --force = false # Overwrite existing --level: string = "auto" # "workspace", "infra", or "auto" ]: nothing -> record { # Determine target layer let layer_info = (determine-layer --workspace $target_path --infra $target_path --level $level) let load_path = $layer_info.path print $"Loading clusters into ($layer_info.layer) layer: ($load_path)" # Validate target path if not ($load_path | path exists) { error make { msg: $"Target path not found: ($load_path)" } } # Validate clusters exist in system let validation = (validate-clusters $clusters) if not $validation.valid { error make { msg: $"Missing clusters: ($validation.missing)" } } # Create clusters directory at target layer let clusters_dir = ($load_path | path join ".clusters") mkdir $clusters_dir # Load each cluster let results = ($clusters | each { |name| load-single-cluster $load_path $name $force $layer_info.layer }) # Generate imports file generate-clusters-imports $load_path $clusters $layer_info.layer # Create/update manifest update-clusters-manifest $load_path $clusters $layer_info.layer { target: $load_path layer: $layer_info.layer loaded: ($results | where status == "success" | get name) failed: ($results | where status == "error" | get name) summary: $"Loaded (($results | where status == 'success' | length)) of (($clusters | length)) clusters at ($layer_info.layer) layer" } } # Load a single cluster def load-single-cluster [target_path: string, name: string, force: bool, layer: string]: nothing -> record { let result = (do { let cluster_info = (get-cluster-info $name) let target_dir = ($target_path | path join ".clusters" $name) # Check if already exists if ($target_dir | path exists) and (not $force) { print $"⚠️ Cluster ($name) already loaded at ($layer) layer (add --force flag to overwrite)" return { name: $name status: "skipped" message: "already exists" } } # Copy KCL files and directories cp -r $cluster_info.kcl_path $target_dir print $"✅ Loaded cluster: ($name) (type: ($cluster_info.cluster_type))" { name: $name status: "success" path: $target_dir version: $cluster_info.version type: $cluster_info.cluster_type components: $cluster_info.components } } | complete) if $result.exit_code != 0 { print $"❌ Failed to load cluster ($name): ($result.stderr)" { name: $name status: "error" error: $result.stderr } } else { $result.stdout | from json } } # Generate clusters.k import file def generate-clusters-imports [target_path: string, clusters: list, layer: string] { # Generate individual imports for each cluster let imports = ($clusters | each { |name| # Check if the cluster main file exists let main_file = ($target_path | path join ".clusters" $name ($name + ".k")) if ($main_file | path exists) { $"import .clusters.($name).($name) as ($name)_cluster" } else { # Fallback to directory-based import $"import .clusters.($name) as ($name)_cluster" } } | str join "\n") # Generate schema exports let exports = ($clusters | each { |name| $" ($name): ($name)_cluster" } | str join ",\n") # Create the complete imports file let content = $"# Auto-generated cluster imports ($layer) layer # Generated: (date now | format date '%Y-%m-%d %H:%M:%S') # Loaded clusters: ($clusters | str join ', ') ($imports) # Export all loaded cluster schemas clusters = { ($exports) } clusters" # Save the imports file $content | save -f ($target_path | path join "clusters.k") # Also create individual alias files for easier direct imports for $name in $clusters { let alias_content = $"# Cluster alias for ($name) # Generated: (date now | format date '%Y-%m-%d %H:%M:%S') # Layer: ($layer) import .clusters.($name) as ($name) # Re-export for convenience ($name)" $alias_content | save -f ($target_path | path join $"cluster_($name).k") } } # Update clusters manifest def update-clusters-manifest [target_path: string, clusters: list, layer: string] { let manifest_dir = ($target_path | path join ".manifest") mkdir $manifest_dir let manifest_path = ($manifest_dir | path join "clusters.yaml") let existing = if ($manifest_path | path exists) { open $manifest_path } else { {} } let cluster_entries = ($clusters | each { |name| let info = (get-cluster-info $name) { name: $name version: $info.version type: $info.cluster_type components: $info.components layer: $layer loaded_at: (date now | format date '%Y-%m-%d %H:%M:%S') source_path: $info.kcl_path } }) let manifest = { loaded_clusters: $cluster_entries last_updated: (date now | format date '%Y-%m-%d %H:%M:%S') target_path: $target_path layer: $layer } $manifest | to yaml | save -f $manifest_path } # Remove cluster from workspace export def unload-cluster [workspace: string, name: string]: nothing -> record { let target_dir = ($workspace | path join ".clusters" $name) if not ($target_dir | path exists) { error make { msg: $"Cluster ($name) not loaded in workspace" } } rm -rf $target_dir # Update manifest and imports let manifest_path = ($workspace | path join "clusters.manifest.yaml") if ($manifest_path | path exists) { let manifest = (open $manifest_path) let updated_clusters = ($manifest.loaded_clusters | where name != $name) if ($updated_clusters | is-empty) { rm $manifest_path rm ($workspace | path join "clusters.k") } else { let updated_manifest = ($manifest | update loaded_clusters $updated_clusters) $updated_manifest | to yaml | save $manifest_path # Regenerate imports let names = ($updated_clusters | get name) # Determine layer from manifest or default to workspace let layer = ($manifest.layer? | default "workspace") generate-clusters-imports $workspace $names $layer } } print $"✅ Unloaded cluster: ($name)" { name: $name status: "unloaded" workspace: $workspace } } # List loaded clusters in workspace export def list-loaded-clusters [workspace: string]: nothing -> list { let manifest_path = ($workspace | path join "clusters.manifest.yaml") if not ($manifest_path | path exists) { return [] } let manifest = (open $manifest_path) $manifest.loaded_clusters? | default [] } # Clone cluster configuration for customization export def clone-cluster [ workspace: string, source_name: string, target_name: string ]: nothing -> record { # Check if source cluster is loaded let loaded = (list-loaded-clusters $workspace) let source_loaded = ($loaded | where name == $source_name | length) > 0 if not $source_loaded { error make { msg: $"Source cluster ($source_name) not loaded in workspace" } } let source_dir = ($workspace | path join ".clusters" $source_name) let target_dir = ($workspace | path join ".clusters" $target_name) if ($target_dir | path exists) { error make { msg: $"Target cluster ($target_name) already exists" } } # Copy cluster files cp -r $source_dir $target_dir # Update cluster name in schema files let schema_files = (ls ($target_dir | path join "*.k") | get name) for $file in $schema_files { let content = (open $file) let updated = ($content | str replace $source_name $target_name) $updated | save $file } # Update manifest let current_clusters = (list-loaded-clusters $workspace | get name) let updated_clusters = ($current_clusters | append $target_name) # Determine layer from loaded cluster manifests or default to workspace let layer = "workspace" # Default for cloned clusters update-clusters-manifest $workspace $updated_clusters $layer # Regenerate imports generate-clusters-imports $workspace $updated_clusters $layer print $"✅ Cloned cluster: ($source_name) → ($target_name)" { source: $source_name target: $target_name status: "cloned" workspace: $workspace } }