376 lines
10 KiB
Plaintext
376 lines
10 KiB
Plaintext
|
|
#!/usr/bin/env nu
|
|||
|
|
# VAPORA CI/CD Pipeline Integration
|
|||
|
|
# Validates, builds, and tests deployment artifacts
|
|||
|
|
# Designed for GitHub Actions, GitLab CI, Jenkins integration
|
|||
|
|
# Version: 1.0.0
|
|||
|
|
|
|||
|
|
def main [
|
|||
|
|
--mode: string = "multiuser"
|
|||
|
|
--artifact-dir: string = "artifacts"
|
|||
|
|
--test-deploy: bool = false
|
|||
|
|
] {
|
|||
|
|
let start_time = (date now)
|
|||
|
|
print "🔧 VAPORA CI/CD Pipeline"
|
|||
|
|
print $"Mode: ($mode) | Artifact Dir: ($artifact_dir)"
|
|||
|
|
print $"Timestamp: ($start_time | format date '%Y-%m-%d %H:%M:%S')"
|
|||
|
|
print ""
|
|||
|
|
|
|||
|
|
# Step 1: Validate Nickel configurations
|
|||
|
|
print "Step 1️⃣ - Validating Nickel configurations..."
|
|||
|
|
validate-nickel-configs
|
|||
|
|
|
|||
|
|
# Step 2: Generate configurations
|
|||
|
|
print "Step 2️⃣ - Generating configurations..."
|
|||
|
|
generate-all-configs $artifact_dir
|
|||
|
|
|
|||
|
|
# Step 3: Validate all outputs
|
|||
|
|
print "Step 3️⃣ - Validating all outputs..."
|
|||
|
|
validate-all-outputs $artifact_dir
|
|||
|
|
|
|||
|
|
# Step 4: Render templates
|
|||
|
|
print "Step 4️⃣ - Rendering templates..."
|
|||
|
|
render-all-templates $artifact_dir
|
|||
|
|
|
|||
|
|
# Step 5: Test deployment artifacts
|
|||
|
|
if $test_deploy {
|
|||
|
|
print "Step 5️⃣ - Testing deployment (dry-run)..."
|
|||
|
|
test-deployment-artifacts $artifact_dir
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Step 6: Generate reports
|
|||
|
|
print "Step 6️⃣ - Generating reports..."
|
|||
|
|
generate-reports $artifact_dir
|
|||
|
|
|
|||
|
|
let end_time = (date now)
|
|||
|
|
let duration = ($end_time - $start_time)
|
|||
|
|
|
|||
|
|
print ""
|
|||
|
|
print "✅ CI/CD Pipeline Complete"
|
|||
|
|
print $"Duration: ($duration)"
|
|||
|
|
print $"Artifacts: ($artifact_dir)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def validate-nickel-configs: nothing {
|
|||
|
|
print " 🔍 Checking Nickel configurations..."
|
|||
|
|
|
|||
|
|
let configs = [
|
|||
|
|
"schemas/vapora/main.ncl"
|
|||
|
|
"schemas/vapora/backend.ncl"
|
|||
|
|
"schemas/vapora/agents.ncl"
|
|||
|
|
"schemas/vapora/llm-router.ncl"
|
|||
|
|
"schemas/platform/common/helpers.ncl"
|
|||
|
|
"schemas/platform/schemas/common/server.ncl"
|
|||
|
|
"schemas/platform/schemas/common/database.ncl"
|
|||
|
|
"schemas/platform/schemas/common/monitoring.ncl"
|
|||
|
|
"schemas/platform/schemas/common/security.ncl"
|
|||
|
|
"schemas/platform/schemas/common/storage.ncl"
|
|||
|
|
"schemas/platform/configs/vapora-solo.ncl"
|
|||
|
|
"schemas/platform/configs/vapora-multiuser.ncl"
|
|||
|
|
"schemas/platform/configs/vapora-enterprise.ncl"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
$configs | each { |config|
|
|||
|
|
print $" → ($config)"
|
|||
|
|
let result = do {
|
|||
|
|
nickel typecheck $config
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Typecheck failed: ($result.stderr)"}
|
|||
|
|
}
|
|||
|
|
print $" ✓ Valid"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print " ✓ All Nickel configurations valid"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def generate-all-configs [artifact_dir: string] {
|
|||
|
|
print " 🔨 Generating configurations for all modes..."
|
|||
|
|
|
|||
|
|
let modes = ["solo", "multiuser", "enterprise"]
|
|||
|
|
|
|||
|
|
$modes | each { |mode|
|
|||
|
|
print $" → ($mode) mode"
|
|||
|
|
|
|||
|
|
let result = do {
|
|||
|
|
nickel export $"schemas/platform/configs/vapora-($mode).ncl"
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Export failed for ($mode): ($result.stderr)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let output_path = ($artifact_dir | path join $"config-($mode).json")
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$result.stdout | save -f $output_path
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
error make {msg: $"Failed to save config-($mode).json"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $" ✓ Generated"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print " ✓ All configurations generated"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def validate-all-outputs [artifact_dir: string] {
|
|||
|
|
print " ✅ Validating all JSON outputs..."
|
|||
|
|
|
|||
|
|
let json_files = [
|
|||
|
|
"config-solo.json"
|
|||
|
|
"config-multiuser.json"
|
|||
|
|
"config-enterprise.json"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
$json_files | each { |file|
|
|||
|
|
let path = ($artifact_dir | path join $file)
|
|||
|
|
|
|||
|
|
if not ($path | path exists) {
|
|||
|
|
error make {msg: $"Missing file: ($file)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let result = do {
|
|||
|
|
open $path | to json
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Invalid JSON: ($file)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $" ✓ ($file) valid"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print " ✓ All JSON outputs valid"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def render-all-templates [artifact_dir: string] {
|
|||
|
|
print " 🎨 Rendering Jinja2 templates..."
|
|||
|
|
|
|||
|
|
let modes = ["solo", "multiuser", "enterprise"]
|
|||
|
|
|
|||
|
|
$modes | each { |mode|
|
|||
|
|
let config_path = ($artifact_dir | path join $"config-($mode).json")
|
|||
|
|
|
|||
|
|
# TOML
|
|||
|
|
print $" → ($mode): TOML"
|
|||
|
|
let toml_result = do {
|
|||
|
|
jinja2 schemas/platform/templates/configs/vapora.toml.j2 $config_path
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $toml_result.exit_code != 0 {
|
|||
|
|
error make {msg: $"TOML rendering failed: ($toml_result.stderr)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$toml_result.stdout | save -f ($artifact_dir | path join $"vapora-($mode).toml")
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
error make {msg: "Failed to save TOML"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# YAML
|
|||
|
|
print $" → ($mode): YAML"
|
|||
|
|
let yaml_result = do {
|
|||
|
|
jinja2 schemas/platform/templates/configs/vapora.yaml.j2 $config_path
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $yaml_result.exit_code != 0 {
|
|||
|
|
error make {msg: $"YAML rendering failed: ($yaml_result.stderr)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$yaml_result.stdout | save -f ($artifact_dir | path join $"vapora-($mode).yaml")
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
error make {msg: "Failed to save YAML"}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Kubernetes templates (for all modes, they're the same ConfigMap/Deployment pattern)
|
|||
|
|
print " → Kubernetes: ConfigMap"
|
|||
|
|
let config_path = ($artifact_dir | path join "config-enterprise.json")
|
|||
|
|
let cm_result = do {
|
|||
|
|
jinja2 schemas/platform/templates/kubernetes/configmap.yaml.j2 $config_path
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $cm_result.exit_code != 0 {
|
|||
|
|
error make {msg: $"ConfigMap rendering failed: ($cm_result.stderr)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$cm_result.stdout | save -f ($artifact_dir | path join "configmap.yaml")
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
error make {msg: "Failed to save ConfigMap"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print " → Kubernetes: Deployment"
|
|||
|
|
let deploy_result = do {
|
|||
|
|
jinja2 schemas/platform/templates/kubernetes/deployment.yaml.j2 $config_path
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $deploy_result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Deployment rendering failed: ($deploy_result.stderr)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$deploy_result.stdout | save -f ($artifact_dir | path join "deployment.yaml")
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
error make {msg: "Failed to save Deployment"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Docker Compose
|
|||
|
|
print " → Docker Compose"
|
|||
|
|
let docker_path = ($artifact_dir | path join "config-solo.json")
|
|||
|
|
let dc_result = do {
|
|||
|
|
jinja2 schemas/platform/templates/docker-compose/docker-compose.yaml.j2 $docker_path
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $dc_result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Docker Compose rendering failed: ($dc_result.stderr)"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$dc_result.stdout | save -f ($artifact_dir | path join "docker-compose.yml")
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
error make {msg: "Failed to save Docker Compose"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print " ✓ All templates rendered"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def test-deployment-artifacts [artifact_dir: string] {
|
|||
|
|
print " 🧪 Testing deployment artifacts (dry-run)..."
|
|||
|
|
|
|||
|
|
# Validate YAML with yq
|
|||
|
|
print " → Validating YAML syntax..."
|
|||
|
|
let yaml_files = [
|
|||
|
|
"vapora-solo.yaml"
|
|||
|
|
"vapora-multiuser.yaml"
|
|||
|
|
"vapora-enterprise.yaml"
|
|||
|
|
"configmap.yaml"
|
|||
|
|
"deployment.yaml"
|
|||
|
|
"docker-compose.yml"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
$yaml_files | each { |file|
|
|||
|
|
let path = ($artifact_dir | path join $file)
|
|||
|
|
|
|||
|
|
if ($path | path exists) {
|
|||
|
|
let result = do {
|
|||
|
|
yq eval '.' $path
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Invalid YAML in ($file)"}
|
|||
|
|
}
|
|||
|
|
print $" ✓ ($file)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Test Kubernetes manifests with kubectl dry-run
|
|||
|
|
print " → Testing Kubernetes manifests..."
|
|||
|
|
let cm_path = ($artifact_dir | path join "configmap.yaml")
|
|||
|
|
let deploy_path = ($artifact_dir | path join "deployment.yaml")
|
|||
|
|
|
|||
|
|
if ($cm_path | path exists) {
|
|||
|
|
let result = do {
|
|||
|
|
kubectl apply -f $cm_path --dry-run=client
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Invalid Kubernetes ConfigMap: ($result.stderr)"}
|
|||
|
|
}
|
|||
|
|
print " ✓ ConfigMap (dry-run passed)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($deploy_path | path exists) {
|
|||
|
|
let result = do {
|
|||
|
|
kubectl apply -f $deploy_path --dry-run=client
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $result.exit_code != 0 {
|
|||
|
|
error make {msg: $"Invalid Kubernetes Deployment: ($result.stderr)"}
|
|||
|
|
}
|
|||
|
|
print " ✓ Deployment (dry-run passed)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print " ✓ All tests passed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def generate-reports [artifact_dir: string] {
|
|||
|
|
print " 📋 Generating CI/CD reports..."
|
|||
|
|
|
|||
|
|
# Generate manifest
|
|||
|
|
let manifest_path = ($artifact_dir | path join "MANIFEST.md")
|
|||
|
|
|
|||
|
|
let report = @"
|
|||
|
|
# VAPORA Deployment Artifacts
|
|||
|
|
|
|||
|
|
Generated: (date now | format date '%Y-%m-%d %H:%M:%S')
|
|||
|
|
|
|||
|
|
## Files Generated
|
|||
|
|
|
|||
|
|
### Configurations
|
|||
|
|
- config-solo.json
|
|||
|
|
- config-multiuser.json
|
|||
|
|
- config-enterprise.json
|
|||
|
|
|
|||
|
|
### TOML Outputs
|
|||
|
|
- vapora-solo.toml
|
|||
|
|
- vapora-multiuser.toml
|
|||
|
|
- vapora-enterprise.toml
|
|||
|
|
|
|||
|
|
### YAML Outputs
|
|||
|
|
- vapora-solo.yaml
|
|||
|
|
- vapora-multiuser.yaml
|
|||
|
|
- vapora-enterprise.yaml
|
|||
|
|
|
|||
|
|
### Kubernetes Manifests
|
|||
|
|
- configmap.yaml
|
|||
|
|
- deployment.yaml
|
|||
|
|
|
|||
|
|
### Docker Compose
|
|||
|
|
- docker-compose.yml
|
|||
|
|
|
|||
|
|
## Deployment Modes
|
|||
|
|
|
|||
|
|
| Mode | Solo | Multiuser | Enterprise |
|
|||
|
|
|------|------|-----------|------------|
|
|||
|
|
| Host | 127.0.0.1 | 0.0.0.0 | 0.0.0.0 |
|
|||
|
|
| Workers | 2 | 4 | 8 |
|
|||
|
|
| NATS | disabled | enabled | enabled |
|
|||
|
|
| Cost Tracking | disabled | enabled | enabled |
|
|||
|
|
| Max Agents | 3 | 10 | 50 |
|
|||
|
|
|
|||
|
|
## Status
|
|||
|
|
|
|||
|
|
✅ All configurations generated
|
|||
|
|
✅ All templates rendered
|
|||
|
|
✅ All outputs validated
|
|||
|
|
"@
|
|||
|
|
|
|||
|
|
do {
|
|||
|
|
$report | save -f $manifest_path
|
|||
|
|
} | complete | if $in.exit_code != 0 {
|
|||
|
|
print " ⚠️ Failed to save manifest"
|
|||
|
|
} else {
|
|||
|
|
print $" ✓ Manifest: ($manifest_path)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# List all artifacts
|
|||
|
|
print " 📦 Artifacts summary:"
|
|||
|
|
let artifacts = do {
|
|||
|
|
ls $artifact_dir -la
|
|||
|
|
} | complete
|
|||
|
|
|
|||
|
|
if $artifacts.exit_code == 0 {
|
|||
|
|
$artifacts.stdout | lines | each { |line|
|
|||
|
|
if ($line | str contains ".json") or ($line | str contains ".yaml") or ($line | str contains ".toml") or ($line | str contains ".yml") {
|
|||
|
|
print $" • ($line)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Run main function
|
|||
|
|
main
|