#!/usr/bin/env nu # VAPORA Deployment Pipeline Orchestration # Handles configuration generation, validation, and deployment to all platforms # Version: 1.0.0 def main [ --mode: string = "multiuser" --output-dir: string = "dist" --target: string = "docker" --validate-only: bool = false --dry-run: bool = false ] { let timestamp = (date now | format date '%Y%m%d-%H%M%S') let log_file = ($output_dir | path join $"deploy-($timestamp).log") # Create output directory do { mkdir ($output_dir | path expand) } | complete | if $in.exit_code != 0 { error make {msg: $"Failed to create output directory: ($in.stderr)"} } print $"πŸš€ VAPORA Deployment Pipeline - Mode: ($mode), Target: ($target)" print $"πŸ“ Logging to: ($log_file)" print "" # Step 1: Generate configuration print "Step 1️⃣ - Generating configuration from Nickel..." let config_json = (generate-config $mode $output_dir) if $config_json == null { error make {msg: "Configuration generation failed"} } print "βœ“ Configuration generated" print "" # Step 2: Validate configuration print "Step 2️⃣ - Validating configuration..." let validation = (validate-config $config_json) if not $validation.valid { error make {msg: $"Validation failed: ($validation.errors | str join ', ')"} } print "βœ“ Configuration valid" print "" # Step 3: Render templates based on target print "Step 3️⃣ - Rendering output templates..." let rendered = (render-templates $config_json $mode $output_dir $target) if not $rendered { error make {msg: "Template rendering failed"} } print "βœ“ Templates rendered" print "" # Step 4: Validate rendered outputs print "Step 4️⃣ - Validating rendered outputs..." let output_validation = (validate-outputs $output_dir $target) if not $output_validation.valid { error make {msg: $"Output validation failed: ($output_validation.errors | str join ', ')"} } print "βœ“ Outputs validated" print "" if $validate_only { print "βœ… Validation complete (--validate-only specified)" return } # Step 5: Deploy based on target print "Step 5️⃣ - Deploying..." match $target { "docker" => { print "πŸ“¦ Deploying to Docker Compose..." deploy-docker $mode $output_dir $dry_run } "kubernetes" => { print "☸️ Deploying to Kubernetes..." deploy-kubernetes $mode $output_dir $dry_run } "both" => { print "πŸ“¦ Deploying to Docker Compose..." deploy-docker $mode $output_dir $dry_run print "☸️ Deploying to Kubernetes..." deploy-kubernetes $mode $output_dir $dry_run } _ => { error make {msg: $"Unknown target: ($target). Use 'docker', 'kubernetes', or 'both'"} } } print "" print "βœ… Deployment complete!" print $"Outputs saved to: ($output_dir)" } def generate-config [mode: string, output_dir: string] { let config_file = $"schemas/platform/configs/vapora-($mode).ncl" if not ($config_file | path exists) { error make {msg: $"Config not found: ($config_file)"} } let output_path = ($output_dir | path join $"config-($mode).json") let result = do { nickel export $config_file } | complete if $result.exit_code != 0 { error make {msg: $"Nickel export failed: ($result.stderr)"} } let json_output = $result.stdout do { $json_output | save -f $output_path } | complete | if $in.exit_code != 0 { error make {msg: $"Failed to save config: ($in.stderr)"} } $output_path } def validate-config [config_path: string] { let config = do { open $config_path } | complete if $config.exit_code != 0 { return { valid: false errors: [ $"Failed to parse config: ($config.stderr)" ] } } let parsed = ($config.stdout | from json) let errors = [] # Validate required fields let required_fields = [ "deployment_mode" "backend" "agents" "llm_router" "database" "frontend" ] let missing_fields = $required_fields | where { |field| not ($parsed | has $field) } if ($missing_fields | length) > 0 { return { valid: false errors: [ $"Missing required fields: ($missing_fields | str join ', ')" ] } } # Validate deployment mode let valid_modes = ["solo", "multiuser", "enterprise"] if not ($valid_modes | any { |mode| $mode == $parsed.deployment_mode }) { return { valid: false errors: [ $"Invalid deployment_mode: ($parsed.deployment_mode)" ] } } {valid: true, errors: []} } def render-templates [config_path: string, mode: string, output_dir: string, target: string] { let config = (open $config_path) # Render TOML print " β†’ Rendering TOML configuration..." let toml_result = do { jinja2 schemas/platform/templates/configs/vapora.toml.j2 $config_path } | complete if $toml_result.exit_code != 0 { print $" βœ— TOML rendering failed: ($toml_result.stderr)" return false } do { $toml_result.stdout | save -f ($output_dir | path join $"vapora-($mode).toml") } | complete | if $in.exit_code != 0 { return false } print " βœ“ TOML" # Render YAML print " β†’ Rendering YAML configuration..." let yaml_result = do { jinja2 schemas/platform/templates/configs/vapora.yaml.j2 $config_path } | complete if $yaml_result.exit_code != 0 { print $" βœ— YAML rendering failed: ($yaml_result.stderr)" return false } do { $yaml_result.stdout | save -f ($output_dir | path join $"vapora-($mode).yaml") } | complete | if $in.exit_code != 0 { return false } print " βœ“ YAML" # Render Kubernetes templates if needed if ($target == "kubernetes") or ($target == "both") { print " β†’ Rendering Kubernetes ConfigMap..." let cm_result = do { jinja2 schemas/platform/templates/kubernetes/configmap.yaml.j2 $config_path } | complete if $cm_result.exit_code != 0 { print $" βœ— ConfigMap rendering failed: ($cm_result.stderr)" return false } do { $cm_result.stdout | save -f ($output_dir | path join "configmap.yaml") } | complete | if $in.exit_code != 0 { return false } print " βœ“ ConfigMap" print " β†’ Rendering Kubernetes Deployment..." let deploy_result = do { jinja2 schemas/platform/templates/kubernetes/deployment.yaml.j2 $config_path } | complete if $deploy_result.exit_code != 0 { print $" βœ— Deployment rendering failed: ($deploy_result.stderr)" return false } do { $deploy_result.stdout | save -f ($output_dir | path join "deployment.yaml") } | complete | if $in.exit_code != 0 { return false } print " βœ“ Deployment" } # Render Docker Compose if needed if ($target == "docker") or ($target == "both") { print " β†’ Rendering Docker Compose..." let dc_result = do { jinja2 schemas/platform/templates/docker-compose/docker-compose.yaml.j2 $config_path } | complete if $dc_result.exit_code != 0 { print $" βœ— Docker Compose rendering failed: ($dc_result.stderr)" return false } do { $dc_result.stdout | save -f ($output_dir | path join "docker-compose.yml") } | complete | if $in.exit_code != 0 { return false } print " βœ“ Docker Compose" } true } def validate-outputs [output_dir: string, target: string] { let errors = [] # Validate YAML files let yaml_files = if ($target == "docker") { ["vapora-solo.yaml", "vapora-multiuser.yaml", "vapora-enterprise.yaml"] } else if ($target == "kubernetes") { ["configmap.yaml", "deployment.yaml"] } else { ["vapora-solo.yaml", "configmap.yaml", "deployment.yaml"] } $yaml_files | each { |file| let path = ($output_dir | path join $file) if not ($path | path exists) { $errors | append $"Missing file: ($file)" } else { let validate = do { yq eval '.' $path } | complete if $validate.exit_code != 0 { $errors | append $"Invalid YAML in ($file): ($validate.stderr)" } } } { valid: ($errors | length) == 0 errors: $errors } } def deploy-docker [mode: string, output_dir: string, dry_run: bool] { let compose_file = ($output_dir | path join "docker-compose.yml") if not ($compose_file | path exists) { error make {msg: "Docker Compose file not found"} } print " πŸ“ Docker Compose file: $compose_file" if $dry_run { print " πŸ” [DRY RUN] Would execute: docker compose -f $compose_file up -d" return } print " πŸš€ Starting Docker Compose services..." let result = do { docker compose -f $compose_file up -d } | complete if $result.exit_code != 0 { error make {msg: $"Docker Compose failed: ($result.stderr)"} } print " βœ“ Services started" print "" print " πŸ“Š Running services:" do { docker compose -f $compose_file ps } | complete | if $in.exit_code == 0 { print $in.stdout } } def deploy-kubernetes [mode: string, output_dir: string, dry_run: bool] { let configmap_file = ($output_dir | path join "configmap.yaml") let deployment_file = ($output_dir | path join "deployment.yaml") if not ($configmap_file | path exists) { error make {msg: "Kubernetes ConfigMap not found"} } if not ($deployment_file | path exists) { error make {msg: "Kubernetes Deployment not found"} } # Ensure namespace exists if $dry_run { print " πŸ” [DRY RUN] Would create namespace: vapora" } else { do { kubectl create namespace vapora --dry-run=client -o yaml | kubectl apply -f - } | complete | if $in.exit_code != 0 { print " ⚠️ Namespace creation (may already exist)" } } # Apply ConfigMap print " πŸ“ Applying ConfigMap..." if $dry_run { print " πŸ” [DRY RUN] Would apply: ($configmap_file)" } else { let cm_result = do { kubectl apply -f $configmap_file } | complete if $cm_result.exit_code != 0 { error make {msg: $"ConfigMap deployment failed: ($cm_result.stderr)"} } print " βœ“ ConfigMap applied" } # Apply Deployments print " πŸ“ Applying Deployments..." if $dry_run { print " πŸ” [DRY RUN] Would apply: ($deployment_file)" } else { let deploy_result = do { kubectl apply -f $deployment_file } | complete if $deploy_result.exit_code != 0 { error make {msg: $"Deployment failed: ($deploy_result.stderr)"} } print " βœ“ Deployments applied" } print "" print " πŸ“Š Deployment status:" do { kubectl get deployment -n vapora -o wide } | complete | if $in.exit_code == 0 { print $in.stdout } } # Run main function main