provisioning/scripts/platform-generate-manifests.nu

389 lines
17 KiB
Text
Raw Permalink Normal View History

#!/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
}