389 lines
17 KiB
Text
389 lines
17 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
|
||
|
|
use std log
|
||
|
|
|
||
|
|
# Helper: Determine platform configuration base directory
|
||
|
|
# Uses PROVISIONING_USER_PLATFORM env var if set, otherwise defaults to OS-specific path
|
||
|
|
def get-platform-base-dir [] {
|
||
|
|
let env_path = ($env.PROVISIONING_USER_PLATFORM? | default "")
|
||
|
|
|
||
|
|
if ($env_path | is-empty) {
|
||
|
|
# Fallback to OS-specific default location
|
||
|
|
let home = $nu.home-dir
|
||
|
|
let os = $nu.os-info.name
|
||
|
|
|
||
|
|
if $os == "macos" {
|
||
|
|
$"($home)/Library/Application Support/provisioning/platform"
|
||
|
|
} else {
|
||
|
|
# Linux and other Unix-like systems
|
||
|
|
$"($home)/.config/provisioning/platform"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$env_path
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Display help information
|
||
|
|
def show-help [] {
|
||
|
|
let home = $nu.home-dir
|
||
|
|
let os = $nu.os-info.name
|
||
|
|
let macos_config = $"($home)/Library/Application Support/provisioning/platform"
|
||
|
|
let linux_config = $"($home)/.config/provisioning/platform"
|
||
|
|
let default_config = if $os == "macos" { $macos_config } else { $linux_config }
|
||
|
|
|
||
|
|
print "╭─ PROVISIONING MANIFEST GENERATOR ──────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ Generate Kubernetes and Docker Compose manifests from Nickel templates"
|
||
|
|
print "│"
|
||
|
|
print "├─ PLATFORM-SPECIFIC PATHS ─────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ macOS:"
|
||
|
|
print "│ Config Source: ~/Library/Application Support/provisioning/platform/config/"
|
||
|
|
print "│ Output Directory: ~/Library/Application Support/provisioning/platform/"
|
||
|
|
print "│"
|
||
|
|
print "│ Linux:"
|
||
|
|
print "│ Config Source: ~/.config/provisioning/platform/config/"
|
||
|
|
print "│ Output Directory: ~/.config/provisioning/platform/"
|
||
|
|
print "│"
|
||
|
|
print "├─ USAGE ───────────────────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ Get help:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu help"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu -h (shows Nushell built-in help)"
|
||
|
|
print "│"
|
||
|
|
print "│ Generate both Docker and Kubernetes (default):"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu"
|
||
|
|
print "│"
|
||
|
|
print "│ Generate specific target:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu docker # Docker Compose only"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu kubernetes # Kubernetes only"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu all # Both (same as no args)"
|
||
|
|
print "│"
|
||
|
|
print "│ With validation:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu docker --validate"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu all --validate"
|
||
|
|
print "│"
|
||
|
|
print "│ Custom paths:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu kubernetes \\"
|
||
|
|
print "│ --config-dir /path/to/configs \\"
|
||
|
|
print "│ --output-dir /path/to/output"
|
||
|
|
print "│"
|
||
|
|
print "├─ OPTIONS ─────────────────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ TARGET (optional):"
|
||
|
|
print "│ docker Generate Docker Compose manifests only"
|
||
|
|
print "│ kubernetes (or k8s) Generate Kubernetes manifests only"
|
||
|
|
print "│ all Generate both Docker and Kubernetes"
|
||
|
|
print "│ (none) Generate both (default behavior)"
|
||
|
|
print "│"
|
||
|
|
print "│ FLAGS:"
|
||
|
|
print "│ help Show this help message"
|
||
|
|
print "│ -h Show Nushell built-in help"
|
||
|
|
print "│ --validate Validate generated manifests (Docker, Kubernetes)"
|
||
|
|
print "│ --config-dir <path> Source directory for user configs"
|
||
|
|
print $"│ Default: ($default_config)/config/"
|
||
|
|
print "│"
|
||
|
|
print "│ --output-dir <path> Destination directory for manifests"
|
||
|
|
print $"│ Default: ($default_config)/"
|
||
|
|
print "│"
|
||
|
|
print "├─ ENVIRONMENT VARIABLES ────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ PROVISIONING Provisioning repository root"
|
||
|
|
print "│ Default: Current working directory"
|
||
|
|
print "│"
|
||
|
|
print "├─ OUTPUT ──────────────────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ Generated Files:"
|
||
|
|
print "│ • docker-compose.yml Docker Compose stack configuration"
|
||
|
|
print "│ • k8s/ Kubernetes manifests directory"
|
||
|
|
print "│ ├─ namespace.yaml"
|
||
|
|
print "│ ├─ resource-quota.yaml"
|
||
|
|
print "│ ├─ rbac.yaml"
|
||
|
|
print "│ ├─ network-policy.yaml"
|
||
|
|
print "│ ├─ orchestrator-*.yaml"
|
||
|
|
print "│ ├─ control-center-*.yaml"
|
||
|
|
print "│ ├─ mcp-server-*.yaml"
|
||
|
|
print "│ └─ platform-ingress.yaml"
|
||
|
|
print "│"
|
||
|
|
print "├─ EXAMPLES ─────────────────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ 1. Generate both Docker and Kubernetes (default):"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu"
|
||
|
|
print "│"
|
||
|
|
print "│ 2. Generate Docker Compose only:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu docker"
|
||
|
|
print "│"
|
||
|
|
print "│ 3. Generate Kubernetes manifests only:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu kubernetes"
|
||
|
|
print "│"
|
||
|
|
print "│ 4. Generate both with validation:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu all --validate"
|
||
|
|
print "│"
|
||
|
|
print "│ 5. Docker Compose with custom output directory:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu docker \\"
|
||
|
|
print "│ --output-dir /tmp/manifests"
|
||
|
|
print "│"
|
||
|
|
print "│ 6. Kubernetes with custom config and output directories:"
|
||
|
|
print "│ nu scripts/platform_generate-manifests.nu kubernetes \\"
|
||
|
|
print "│ --config-dir ~/.provisioning-custom/config \\"
|
||
|
|
print "│ --output-dir /tmp/k8s-manifests \\"
|
||
|
|
print "│ --validate"
|
||
|
|
print "│"
|
||
|
|
print "├─ REQUIREMENTS ─────────────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ • Nushell 0.110.0+"
|
||
|
|
print "│ • Nickel CLI"
|
||
|
|
print "│ • PROVISIONING environment variable or current directory as repo root"
|
||
|
|
print "│"
|
||
|
|
print "│ Optional (for validation):"
|
||
|
|
print "│ • Docker and Docker Compose (for docker-compose validation)"
|
||
|
|
print "│ • kubectl (for Kubernetes validation)"
|
||
|
|
print "│"
|
||
|
|
print "├─ TROUBLESHOOTING ──────────────────────────────────────────────────────"
|
||
|
|
print "│"
|
||
|
|
print "│ Config directory not found?"
|
||
|
|
print "│ → Check that you have user configs in the default location"
|
||
|
|
print "│ → Or use --config-dir to specify a custom path"
|
||
|
|
print "│"
|
||
|
|
print "│ Docker/Kubernetes validation failing?"
|
||
|
|
print "│ → Ensure Docker and kubectl are installed"
|
||
|
|
print "│ → Run without --validate flag to skip validation"
|
||
|
|
print "│"
|
||
|
|
print "│ Template rendering errors?"
|
||
|
|
print "│ → Verify PROVISIONING environment variable is set:"
|
||
|
|
print "│ export PROVISIONING=/path/to/provisioning"
|
||
|
|
print "│ → Check that templates exist in:"
|
||
|
|
print "│ \$PROVISIONING/schemas/platform/templates/"
|
||
|
|
print "│"
|
||
|
|
print "╰────────────────────────────────────────────────────────────────────────"
|
||
|
|
}
|
||
|
|
|
||
|
|
def generate-manifests [
|
||
|
|
target?: string # What to generate: "docker" (docker-compose only), "kubernetes" (k8s only), or "all" (both)
|
||
|
|
--validate = false # Validate generated manifests
|
||
|
|
--output-dir: string # Output directory for manifests
|
||
|
|
--config-dir: string # Config directory with user service configs
|
||
|
|
] {
|
||
|
|
# Use PROVISIONING environment variable for repo root, fallback to current directory
|
||
|
|
let provisioning_root = $env.PROVISIONING? | default (pwd)
|
||
|
|
|
||
|
|
# Determine output directory (where manifests are written)
|
||
|
|
let platform_output_dir = if ($output_dir == null or ($output_dir | is-empty)) {
|
||
|
|
get-platform-base-dir
|
||
|
|
} else {
|
||
|
|
$output_dir
|
||
|
|
}
|
||
|
|
|
||
|
|
# Determine config directory (where user configs are read from)
|
||
|
|
let platform_config_dir = if ($config_dir == null or ($config_dir | is-empty)) {
|
||
|
|
get-platform-base-dir
|
||
|
|
} else {
|
||
|
|
$config_dir
|
||
|
|
}
|
||
|
|
|
||
|
|
# Templates are now in schemas/platform/templates/
|
||
|
|
let templates_dir = $"($provisioning_root)/schemas/platform/templates"
|
||
|
|
# NICKEL_IMPORT_PATH: provisioning root first (for schemas), then user config directory
|
||
|
|
let nickel_import_path = $"($provisioning_root):($platform_config_dir)/config"
|
||
|
|
|
||
|
|
# Determine what to generate
|
||
|
|
let generate_target = if ($target == null or ($target | is-empty)) {
|
||
|
|
# No argument provided - default to generating both
|
||
|
|
"all"
|
||
|
|
} else {
|
||
|
|
# Validate provided argument
|
||
|
|
let t = ($target | str downcase)
|
||
|
|
if ($t == "docker" or $t == "k8s" or $t == "kubernetes" or $t == "all") {
|
||
|
|
if $t == "k8s" { "kubernetes" } else { $t }
|
||
|
|
} else {
|
||
|
|
log error "Invalid target: $target"
|
||
|
|
log error "Valid options: docker, kubernetes (or k8s), all"
|
||
|
|
error make { msg: "Invalid target specified" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
log info "🔧 Generating manifests from Nickel templates..."
|
||
|
|
log info $" Target: ($generate_target)"
|
||
|
|
log info $" Provisioning repo: ($provisioning_root)"
|
||
|
|
log info $" Config source: ($platform_config_dir)/config/"
|
||
|
|
log info $" Output destination: ($platform_output_dir)"
|
||
|
|
|
||
|
|
# Create output directory if it doesn't exist
|
||
|
|
if not ($platform_output_dir | path exists) {
|
||
|
|
^mkdir -p $platform_output_dir
|
||
|
|
log info $"📁 Created output directory: ($platform_output_dir)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for user config files to inform build context
|
||
|
|
if ($platform_config_dir | path exists) {
|
||
|
|
let config_files = (do { ^find $"($platform_config_dir)/config" -name "*.ncl" -type f 2>/dev/null } | complete).stdout | lines | where { $in != "" }
|
||
|
|
let config_count = $config_files | length
|
||
|
|
if $config_count > 0 {
|
||
|
|
log info $"📋 Found ($config_count) service config files:"
|
||
|
|
$config_files | each { |f|
|
||
|
|
let relative = $f | str replace $"($platform_config_dir)/" ""
|
||
|
|
log info $" • ($relative)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
log warning $"⚠️ Config directory not found: ($platform_config_dir)/config"
|
||
|
|
log info " Manifests will be generated from templates only"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Generate docker-compose (if requested)
|
||
|
|
if ($generate_target == "docker" or $generate_target == "all") {
|
||
|
|
log info "📦 Generating docker-compose.yml..."
|
||
|
|
|
||
|
|
let template = "schemas/platform/templates/docker-compose/platform-stack.solo.yml.ncl"
|
||
|
|
let nickel_path = $"($platform_config_dir)/config:($provisioning_root):($provisioning_root)/schemas/platform"
|
||
|
|
|
||
|
|
let dc_result = (do {
|
||
|
|
with-env { NICKEL_IMPORT_PATH: $nickel_path } {
|
||
|
|
cd $provisioning_root
|
||
|
|
^nickel export --format yaml $template
|
||
|
|
}
|
||
|
|
} | complete)
|
||
|
|
|
||
|
|
if $dc_result.exit_code == 0 {
|
||
|
|
$dc_result.stdout | save --force $"($platform_output_dir)/docker-compose.yml"
|
||
|
|
log info "✅ docker-compose.yml generated"
|
||
|
|
} else {
|
||
|
|
log error $"Failed to generate docker-compose.yml: ($dc_result.stderr)"
|
||
|
|
error make { msg: "Docker Compose generation failed" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Generate Kubernetes manifests (if requested)
|
||
|
|
if ($generate_target == "kubernetes" or $generate_target == "all") {
|
||
|
|
let k8s_dir = $"($platform_output_dir)/k8s"
|
||
|
|
if not ($k8s_dir | path exists) {
|
||
|
|
^mkdir -p $k8s_dir
|
||
|
|
}
|
||
|
|
|
||
|
|
# Kubernetes templates available in new location
|
||
|
|
let k8s_templates = [
|
||
|
|
"namespace",
|
||
|
|
"resource-quota",
|
||
|
|
"rbac",
|
||
|
|
"network-policy",
|
||
|
|
"orchestrator-deployment",
|
||
|
|
"orchestrator-service",
|
||
|
|
"control-center-deployment",
|
||
|
|
"control-center-service",
|
||
|
|
"mcp-server-deployment",
|
||
|
|
"mcp-server-service",
|
||
|
|
"platform-ingress",
|
||
|
|
]
|
||
|
|
|
||
|
|
let nickel_path = $"($platform_config_dir)/config:($provisioning_root):($provisioning_root)/schemas/platform"
|
||
|
|
|
||
|
|
for template in $k8s_templates {
|
||
|
|
log info $"☸️ Generating ($template).yaml..."
|
||
|
|
let template_path = $"schemas/platform/templates/kubernetes/($template).yaml.ncl"
|
||
|
|
|
||
|
|
let k8s_result = (do {
|
||
|
|
with-env { NICKEL_IMPORT_PATH: $nickel_path } {
|
||
|
|
cd $provisioning_root
|
||
|
|
^nickel export --format yaml $template_path
|
||
|
|
}
|
||
|
|
} | complete)
|
||
|
|
|
||
|
|
if $k8s_result.exit_code == 0 {
|
||
|
|
$k8s_result.stdout | save --force $"($k8s_dir)/($template).yaml"
|
||
|
|
log info $"✅ ($template).yaml generated"
|
||
|
|
} else {
|
||
|
|
log error $"Failed to generate ($template).yaml: ($k8s_result.stderr)"
|
||
|
|
if not $validate {
|
||
|
|
# Only fail on missing templates if not in validate-only mode
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if $validate {
|
||
|
|
log info "🔍 Validating manifests..."
|
||
|
|
if ($generate_target == "docker" or $generate_target == "all") {
|
||
|
|
validate-compose $"($platform_output_dir)/docker-compose.yml"
|
||
|
|
}
|
||
|
|
if ($generate_target == "kubernetes" or $generate_target == "all") {
|
||
|
|
let k8s_dir = $"($platform_output_dir)/k8s"
|
||
|
|
validate-kubernetes $k8s_dir
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
log info "✨ Generation complete!"
|
||
|
|
log info $"📂 Output: ($platform_output_dir)"
|
||
|
|
}
|
||
|
|
|
||
|
|
def validate-compose [path: string] {
|
||
|
|
log info "Validating Docker Compose..."
|
||
|
|
|
||
|
|
let docker_check = (do { ^docker --version } | complete)
|
||
|
|
if $docker_check.exit_code != 0 {
|
||
|
|
log warning "⚠️ Docker not found - skipping validation"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
let compose_check = (do { ^docker compose version } | complete)
|
||
|
|
if $compose_check.exit_code != 0 {
|
||
|
|
log warning "⚠️ Docker Compose not available - skipping validation"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
let result = (do { ^docker compose -f $path config } | complete)
|
||
|
|
|
||
|
|
if $result.exit_code == 0 {
|
||
|
|
let service_count = ($result.stdout | lines | where { $in | str contains "container_name:" } | length)
|
||
|
|
log info $"✅ docker-compose.yml is valid"
|
||
|
|
log info $" Services: ($service_count)"
|
||
|
|
} else {
|
||
|
|
log error "❌ Docker Compose validation failed"
|
||
|
|
log error $result.stderr
|
||
|
|
error make { msg: "Docker Compose validation error" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def validate-kubernetes [k8s_dir: string] {
|
||
|
|
log info "Validating Kubernetes manifests..."
|
||
|
|
|
||
|
|
let kubectl_check = (do { ^kubectl version --client } | complete)
|
||
|
|
if $kubectl_check.exit_code != 0 {
|
||
|
|
log warning "⚠️ kubectl not found - skipping validation"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
let result = (do { ^kubectl apply --dry-run=client -f $k8s_dir } | complete)
|
||
|
|
|
||
|
|
if $result.exit_code == 0 {
|
||
|
|
let yaml_files = (do { ^find $k8s_dir -name "*.yaml" -type f } | complete).stdout | lines | where { $in != "" } | length
|
||
|
|
log info "✅ Kubernetes manifests are valid"
|
||
|
|
log info $" Manifest files: ($yaml_files)"
|
||
|
|
} else {
|
||
|
|
if ($result.stderr | str contains "connection refused") {
|
||
|
|
log warning "⚠️ Cannot reach Kubernetes cluster - syntax validation only"
|
||
|
|
} else {
|
||
|
|
log error "❌ Kubernetes validation failed"
|
||
|
|
log error $result.stderr
|
||
|
|
error make { msg: "Kubernetes validation error" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Entry point
|
||
|
|
def main [
|
||
|
|
target?: string # What to generate: 'docker', 'kubernetes' (or 'k8s'), 'all', or 'help'
|
||
|
|
--validate = false # Validate generated manifests
|
||
|
|
--output-dir: string # Output directory for manifests
|
||
|
|
--config-dir: string # Config directory with user service configs
|
||
|
|
] {
|
||
|
|
# Check if help was requested
|
||
|
|
if ($target == "help") or ($target == "-h") or ($target == "--h") {
|
||
|
|
show-help
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
# Delegate to generate-manifests
|
||
|
|
generate-manifests $target --validate=$validate --output-dir=$output_dir --config-dir=$config_dir
|
||
|
|
}
|