#!/usr/bin/env bash set -euo pipefail # Helm template validation script for Bifrost # Validates all storage and vector store combinations render correctly echo "🔍 Validating Helm Chart Templates..." echo "======================================" # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Track test results TESTS_PASSED=0 TESTS_FAILED=0 # Function to report test result report_result() { local test_name=$1 local result=$2 if [ "$result" -eq 0 ]; then echo -e "${GREEN}✅ $test_name${NC}" TESTS_PASSED=$((TESTS_PASSED + 1)) else echo -e "${RED}❌ $test_name${NC}" TESTS_FAILED=$((TESTS_FAILED + 1)) fi } # Function to test a helm template combination test_template() { local test_name=$1 shift local helm_args=("$@") if helm template bifrost ./helm-charts/bifrost \ --set image.tag=v1.0.0 \ "${helm_args[@]}" \ > /tmp/helm-template-output.yaml 2>&1; then report_result "$test_name" 0 return 0 else report_result "$test_name" 1 echo -e "${YELLOW} Error output:${NC}" head -10 /tmp/helm-template-output.yaml | sed 's/^/ /' return 1 fi } # 1. Storage Combinations (9 tests) echo "" echo -e "${CYAN}📦 1/6 - Testing Storage Combinations (9 tests)...${NC}" echo "---------------------------------------------------" # config=no, logs=no test_template "config=no, logs=no" \ --set storage.configStore.enabled=false \ --set storage.logsStore.enabled=false \ --set postgresql.enabled=false # config=no, logs=sqlite test_template "config=no, logs=sqlite" \ --set storage.configStore.enabled=false \ --set storage.logsStore.enabled=true \ --set storage.mode=sqlite \ --set postgresql.enabled=false # config=no, logs=postgres test_template "config=no, logs=postgres" \ --set storage.configStore.enabled=false \ --set storage.logsStore.enabled=true \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass # config=sqlite, logs=no test_template "config=sqlite, logs=no" \ --set storage.configStore.enabled=true \ --set storage.logsStore.enabled=false \ --set storage.mode=sqlite \ --set postgresql.enabled=false # config=sqlite, logs=sqlite test_template "config=sqlite, logs=sqlite" \ --set storage.configStore.enabled=true \ --set storage.logsStore.enabled=true \ --set storage.mode=sqlite \ --set postgresql.enabled=false # config=sqlite, logs=postgres test_template "config=sqlite, logs=postgres" \ --set storage.configStore.enabled=true \ --set storage.logsStore.enabled=true \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass # config=postgres, logs=no test_template "config=postgres, logs=no" \ --set storage.configStore.enabled=true \ --set storage.logsStore.enabled=false \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass # config=postgres, logs=sqlite test_template "config=postgres, logs=sqlite" \ --set storage.configStore.enabled=true \ --set storage.logsStore.enabled=true \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass # config=postgres, logs=postgres test_template "config=postgres, logs=postgres" \ --set storage.configStore.enabled=true \ --set storage.logsStore.enabled=true \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass # 2. Vector Store Combinations (6 tests) echo "" echo -e "${CYAN}🗄️ 2/6 - Testing Vector Store Combinations (6 tests)...${NC}" echo "--------------------------------------------------------" # Weaviate test_template "vectorStore=weaviate" \ --set vectorStore.enabled=true \ --set vectorStore.type=weaviate \ --set vectorStore.weaviate.enabled=true # Redis test_template "vectorStore=redis" \ --set vectorStore.enabled=true \ --set vectorStore.type=redis \ --set vectorStore.redis.enabled=true # Qdrant test_template "vectorStore=qdrant" \ --set vectorStore.enabled=true \ --set vectorStore.type=qdrant \ --set vectorStore.qdrant.enabled=true # postgres + weaviate test_template "postgres + weaviate" \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass \ --set vectorStore.enabled=true \ --set vectorStore.type=weaviate \ --set vectorStore.weaviate.enabled=true # postgres + qdrant test_template "postgres + qdrant" \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass \ --set vectorStore.enabled=true \ --set vectorStore.type=qdrant \ --set vectorStore.qdrant.enabled=true # sqlite + qdrant test_template "sqlite + qdrant" \ --set storage.mode=sqlite \ --set postgresql.enabled=false \ --set vectorStore.enabled=true \ --set vectorStore.type=qdrant \ --set vectorStore.qdrant.enabled=true # 3. Special Configurations (7 tests) echo "" echo -e "${CYAN}⚙️ 3/6 - Testing Special Configurations (7 tests)...${NC}" echo "-----------------------------------------------------" # semantic cache: direct mode (dimension: 1, no provider/keys) test_template "semanticCache: direct mode (dimension: 1)" \ --set bifrost.plugins.semanticCache.enabled=true \ --set bifrost.plugins.semanticCache.config.dimension=1 \ --set bifrost.plugins.semanticCache.config.ttl=30m \ --set vectorStore.enabled=true \ --set vectorStore.type=redis \ --set vectorStore.redis.enabled=true # semantic cache: semantic mode (dimension > 1, requires provider/keys) test_template "semanticCache: semantic mode (dimension: 1536)" \ --set bifrost.plugins.semanticCache.enabled=true \ --set bifrost.plugins.semanticCache.config.dimension=1536 \ --set bifrost.plugins.semanticCache.config.provider=openai \ --set 'bifrost.plugins.semanticCache.config.keys[0]=sk-test' \ --set vectorStore.enabled=true \ --set vectorStore.type=redis \ --set vectorStore.redis.enabled=true # semantic cache: direct mode with redis + postgres test_template "semanticCache: direct mode + postgres" \ --set bifrost.plugins.semanticCache.enabled=true \ --set bifrost.plugins.semanticCache.config.dimension=1 \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass \ --set vectorStore.enabled=true \ --set vectorStore.type=redis \ --set vectorStore.redis.enabled=true # sqlite + persistence + autoscaling (StatefulSet HPA) test_template "sqlite + persistence + autoscaling (StatefulSet)" \ --set storage.mode=sqlite \ --set storage.persistence.enabled=true \ --set postgresql.enabled=false \ --set autoscaling.enabled=true \ --set autoscaling.minReplicas=2 \ --set autoscaling.maxReplicas=5 # postgres + autoscaling (Deployment HPA) test_template "postgres + autoscaling (Deployment)" \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass \ --set autoscaling.enabled=true \ --set autoscaling.minReplicas=2 \ --set autoscaling.maxReplicas=5 # ingress enabled test_template "ingress enabled" \ --set ingress.enabled=true \ --set ingress.className=nginx \ --set 'ingress.hosts[0].host=bifrost.example.com' \ --set 'ingress.hosts[0].paths[0].path=/' \ --set 'ingress.hosts[0].paths[0].pathType=Prefix' # full production-like config test_template "production-like config" \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass \ --set vectorStore.enabled=true \ --set vectorStore.type=qdrant \ --set vectorStore.qdrant.enabled=true \ --set autoscaling.enabled=true \ --set ingress.enabled=true \ --set ingress.className=nginx \ --set 'ingress.hosts[0].host=bifrost.example.com' \ --set 'ingress.hosts[0].paths[0].path=/' \ --set 'ingress.hosts[0].paths[0].pathType=Prefix' # 4. New Property Rendering (Gap 1-8 tests) echo "" echo -e "${CYAN}🆕 4/6 - Testing New Property Rendering (Gap 1-8)...${NC}" echo "-----------------------------------------------------" # Gap 1+2: Client new properties test_template "client: new properties (Gap 1+2)" \ --set bifrost.client.asyncJobResultTTL=300 \ --set 'bifrost.client.requiredHeaders[0]=X-Request-ID' \ --set 'bifrost.client.loggingHeaders[0]=X-Trace-ID' \ --set 'bifrost.client.allowedHeaders[0]=Authorization' \ --set bifrost.client.mcpAgentDepth=5 \ --set bifrost.client.mcpToolExecutionTimeout=30 \ --set bifrost.client.mcpCodeModeBindingLevel=server \ --set bifrost.client.mcpToolSyncInterval=60 \ --set bifrost.client.hideDeletedVirtualKeysInFilters=true # Gap 3: OTel plugin with new fields test_template "otel: headers + tls_ca_cert + insecure (Gap 3)" \ --set bifrost.plugins.otel.enabled=true \ --set bifrost.plugins.otel.config.collector_url=otel:4317 \ --set bifrost.plugins.otel.config.trace_type=genai_extension \ --set bifrost.plugins.otel.config.protocol=grpc \ --set 'bifrost.plugins.otel.config.headers.Authorization=Bearer token' \ --set bifrost.plugins.otel.config.tls_ca_cert=/certs/ca.pem \ --set bifrost.plugins.otel.config.insecure=true # Gap 4: Governance plugin with new fields test_template "governance: required_headers + is_enterprise (Gap 4)" \ --set bifrost.plugins.governance.enabled=true \ --set 'bifrost.plugins.governance.config.required_headers[0]=X-Team-ID' \ --set bifrost.plugins.governance.config.is_enterprise=true # Gap 5: Governance modelConfigs + providers test_template "governance: modelConfigs + providers (Gap 5)" \ --set 'bifrost.governance.modelConfigs[0].id=mc-1' \ --set 'bifrost.governance.modelConfigs[0].model_name=gpt-4o' \ --set 'bifrost.governance.providers[0].name=openai' # Gap 6: MCP new fields test_template "mcp: toolSyncInterval + codeModeBindingLevel (Gap 6)" \ --set bifrost.mcp.enabled=true \ --set bifrost.mcp.toolSyncInterval=10m \ --set bifrost.mcp.toolManagerConfig.codeModeBindingLevel=server \ --set 'bifrost.mcp.clientConfigs[0].name=test' \ --set 'bifrost.mcp.clientConfigs[0].connectionType=http' \ --set 'bifrost.mcp.clientConfigs[0].httpConfig.url=http://localhost:3000' \ --set 'bifrost.mcp.clientConfigs[0].clientId=client-1' \ --set 'bifrost.mcp.clientConfigs[0].isCodeModeClient=true' \ --set 'bifrost.mcp.clientConfigs[0].toolSyncInterval=5m' # Gap 7: Cluster with region test_template "cluster: region (Gap 7)" \ --set bifrost.cluster.enabled=true \ --set 'bifrost.cluster.peers[0]=peer-0:7946' \ --set bifrost.cluster.gossip.port=7946 \ --set bifrost.cluster.gossip.config.timeoutSeconds=10 \ --set bifrost.cluster.gossip.config.successThreshold=3 \ --set bifrost.cluster.gossip.config.failureThreshold=3 \ --set bifrost.cluster.region=us-east-1 # Gap 8: Combined production-like with all new fields test_template "combined: all new Gap 1-8 fields" \ --set bifrost.client.asyncJobResultTTL=300 \ --set bifrost.client.mcpAgentDepth=5 \ --set bifrost.client.hideDeletedVirtualKeysInFilters=true \ --set bifrost.plugins.otel.enabled=true \ --set bifrost.plugins.otel.config.collector_url=otel:4317 \ --set bifrost.plugins.otel.config.trace_type=genai_extension \ --set bifrost.plugins.otel.config.protocol=grpc \ --set bifrost.plugins.otel.config.insecure=true \ --set bifrost.plugins.governance.enabled=true \ --set bifrost.plugins.governance.config.is_enterprise=true \ --set bifrost.cluster.enabled=true \ --set 'bifrost.cluster.peers[0]=peer-0:7946' \ --set bifrost.cluster.gossip.port=7946 \ --set bifrost.cluster.gossip.config.timeoutSeconds=10 \ --set bifrost.cluster.gossip.config.successThreshold=3 \ --set bifrost.cluster.gossip.config.failureThreshold=3 \ --set bifrost.cluster.region=us-west-2 # 5. Plugin Name Validation echo "" echo -e "${CYAN}🔌 5/6 - Validating Plugin Names Match Go Registry...${NC}" echo "------------------------------------------------------" # Verify semantic cache plugin renders with correct name ("semantic_cache", not "semantic_cache") # Go registry: plugins/semantic_cache/main.go defines PluginName = "semantic_cache" test_name="semanticCache plugin name matches Go registry (semantic_cache)" if helm template bifrost ./helm-charts/bifrost \ --set image.tag=v1.0.0 \ --set bifrost.plugins.semanticCache.enabled=true \ --set bifrost.plugins.semanticCache.config.dimension=1536 \ --set bifrost.plugins.semanticCache.config.provider=openai \ --set 'bifrost.plugins.semanticCache.config.keys[0]=sk-test' \ --set vectorStore.enabled=true \ --set vectorStore.type=redis \ --set vectorStore.redis.enabled=true \ > /tmp/helm-template-output.yaml 2>&1; then if grep -Eq '"name"[[:space:]]*:[[:space:]]*"semantic_cache"' /tmp/helm-template-output.yaml; then report_result "$test_name" 0 else report_result "$test_name" 1 fi else report_result "$test_name" 1 echo -e "${YELLOW} Error output:${NC}" head -10 /tmp/helm-template-output.yaml | sed 's/^/ /' fi # 6. Custom Plugin Placement and Order Rendering echo "" echo -e "${CYAN}🔧 6/6 - Validating Custom Plugin placement and order Rendering...${NC}" echo "-------------------------------------------------------------------" # Test custom plugin renders successfully with placement and order test_template "custom plugin with placement and order" \ --set 'bifrost.plugins.custom[0].name=my-plugin' \ --set 'bifrost.plugins.custom[0].enabled=true' \ --set 'bifrost.plugins.custom[0].path=/plugins/my-plugin.so' \ --set 'bifrost.plugins.custom[0].placement=pre_builtin' \ --set 'bifrost.plugins.custom[0].order=2' # Verify placement appears in rendered output test_name="custom plugin rendered JSON contains placement field" if helm template bifrost ./helm-charts/bifrost \ --set image.tag=v1.0.0 \ --set 'bifrost.plugins.custom[0].name=my-plugin' \ --set 'bifrost.plugins.custom[0].enabled=true' \ --set 'bifrost.plugins.custom[0].path=/plugins/my-plugin.so' \ --set 'bifrost.plugins.custom[0].placement=pre_builtin' \ --set 'bifrost.plugins.custom[0].order=2' \ > /tmp/helm-template-output.yaml 2>&1; then if grep -Eq '"placement"[[:space:]]*:[[:space:]]*"pre_builtin"' /tmp/helm-template-output.yaml; then report_result "$test_name" 0 else report_result "$test_name" 1 echo -e "${YELLOW} placement field not found in rendered output${NC}" fi else report_result "$test_name" 1 echo -e "${YELLOW} Error output:${NC}" head -10 /tmp/helm-template-output.yaml | sed 's/^/ /' fi # Verify order appears in rendered output test_name="custom plugin rendered JSON contains order field" if helm template bifrost ./helm-charts/bifrost \ --set image.tag=v1.0.0 \ --set 'bifrost.plugins.custom[0].name=my-plugin' \ --set 'bifrost.plugins.custom[0].enabled=true' \ --set 'bifrost.plugins.custom[0].path=/plugins/my-plugin.so' \ --set 'bifrost.plugins.custom[0].placement=pre_builtin' \ --set 'bifrost.plugins.custom[0].order=2' \ > /tmp/helm-template-output.yaml 2>&1; then if grep -Eq '"order"[[:space:]]*:[[:space:]]*2' /tmp/helm-template-output.yaml; then report_result "$test_name" 0 else report_result "$test_name" 1 echo -e "${YELLOW} order field not found in rendered output${NC}" fi else report_result "$test_name" 1 echo -e "${YELLOW} Error output:${NC}" head -10 /tmp/helm-template-output.yaml | sed 's/^/ /' fi # Cleanup rm -f /tmp/helm-template-output.yaml # Final Summary echo "" echo "======================================" echo "🏁 Helm Template Validation Complete!" echo "======================================" echo -e "${GREEN}Passed: $TESTS_PASSED${NC}" echo -e "${RED}Failed: $TESTS_FAILED${NC}" echo "" if [ "$TESTS_FAILED" -gt 0 ]; then echo -e "${RED}❌ Some template validations failed. Please review the output above.${NC}" exit 1 else echo -e "${GREEN}✅ All template validations passed successfully!${NC}" exit 0 fi