#!/usr/bin/env nu # Docker Dockerfile Generator # Generates Dockerfiles from Nickel templates + build configuration # # Usage: # docker-generate-builds.nu extension-registry --mode solo # docker-generate-builds.nu orchestrator --mode cicd # # Environment: # PROVISIONING_ROOT - Optional: override project root detection # # Patterns: # - Result pattern (NO try-catch) # - Pipeline let binding (Nushell 0.110.0+) # - Guards for validation # Search up directory tree for provisioning root (helper) def search-up-for-provisioning [dir: string]: nothing -> string { if ($"($dir)/provisioning/schemas/platform" | path exists) { $"($dir)/provisioning" } else { let parent = ($dir | path dirname) if $parent == $dir { "" } else { search-up-for-provisioning $parent } } } # Detect provisioning project root # Follows same pattern as platform-generate-manifests.nu def get-provisioning-root []: nothing -> string { # 1. Check PROVISIONING environment variable (standard) if ($env.PROVISIONING? != null) { return $env.PROVISIONING } # 2. Check PROVISIONING_ROOT (alternative) if ($env.PROVISIONING_ROOT? != null) { return $env.PROVISIONING_ROOT } # 3. Check if we're in provisioning/ directory let cwd = (pwd) if ($cwd | path basename) == "provisioning" { # We're inside provisioning/, use current dir if ("schemas/platform" | path exists) { return "." } } # 4. Check if provisioning/ exists as subdirectory if ("provisioning/schemas/platform" | path exists) { return "provisioning" } # 5. Search up the directory tree let found = (search-up-for-provisioning $cwd) if $found != "" { return $found } # 6. Fallback to current directory (same as platform-generate-manifests.nu) pwd } # Valid service names const VALID_SERVICES = [ "orchestrator" "control-center" "extension-registry" "mcp-server" "provisioning-daemon" "ai-service" "rag" "vault-service" ] # Valid deployment modes const VALID_MODES = ["solo", "cicd", "enterprise"] # Service name to Nickel schema key mapping const SERVICE_TO_SCHEMA_KEY = { orchestrator: "orchestrator", control-center: "control_center", extension-registry: "extension_registry", mcp-server: "mcp_server", provisioning-daemon: "provisioning_daemon", ai-service: "ai_service", rag: "rag", vault-service: "vault", } # Service name to package directory mapping const SERVICE_TO_DIR = { orchestrator: "orchestrator", control-center: "control-center", extension-registry: "extension-registry", mcp-server: "mcp-server", provisioning-daemon: "daemon", ai-service: "ai-service", rag: "rag", vault-service: "vault-service", } # Generate Dockerfile for a service def main [ service: string, # Service name (e.g., "orchestrator", "extension-registry") --mode: string = "solo", # Deployment mode (solo, cicd, enterprise) ]: nothing -> record { # Guard: Validate service name if not ($service in $VALID_SERVICES) { return { ok: false, err: $"Invalid service: ($service). Valid: ($VALID_SERVICES | str join ', ')", path: "" } } # Guard: Validate mode if not ($mode in $VALID_MODES) { return { ok: false, err: $"Invalid mode: ($mode). Valid: ($VALID_MODES | str join ', ')", path: "" } } # Get schema key for this service let schema_key = $SERVICE_TO_SCHEMA_KEY | get $service # Get target directory for this service let target_dir = $SERVICE_TO_DIR | get $service # Detect provisioning root let prov_root = (get-provisioning-root) # Construct paths let defaults_file = $"($prov_root)/schemas/platform/defaults/($service)-defaults.ncl" let template_file = $"($prov_root)/schemas/platform/templates/docker/Dockerfile.chef.ncl" let output_file = $"($prov_root)/platform/crates/($target_dir)/Dockerfile" # Guard: Check defaults file exists if not ($defaults_file | path exists) { return { ok: false, err: $"Defaults file not found: ($defaults_file)", path: "" } } # Guard: Check template file exists if not ($template_file | path exists) { return { ok: false, err: $"Template file not found: ($template_file)", path: "" } } # Generate Nickel expression to export Dockerfile let nickel_expr = $" let template = import \"($template_file)\" in let defaults = import \"($defaults_file)\" in template defaults.($schema_key).build " # Execute nickel export with Result pattern let result = ( echo $nickel_expr | nickel export --format raw | complete ) if $result.exit_code != 0 { return { ok: false, err: $"Nickel export failed: ($result.stderr)", path: "" } } let dockerfile = $result.stdout # Check if generation succeeded if ($dockerfile | is-empty) { return { ok: false, err: "Generated Dockerfile is empty", path: "" } } # Ensure output directory exists let output_dir = ($output_file | path dirname) if not ($output_dir | path exists) { mkdir $output_dir } # Write Dockerfile $dockerfile | save --force $output_file # Return success { ok: true, err: "", path: $output_file } } # Helper to generate Dockerfiles for all services export def "main all" [ --mode: string = "solo" # Deployment mode ]: nothing -> table { $VALID_SERVICES | each {|service| let result = (main $service --mode $mode) $result | insert service $service } }