406 lines
12 KiB
Plaintext
Raw Normal View History

2026-01-12 03:36:55 +00:00
#!/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