2025-10-07 10:32:04 +01:00

283 lines
8.9 KiB
Plaintext

#!/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<string>,
--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<string>, 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<string>, 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<record> {
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
}
}