Files
bifrost/.github/workflows/scripts/validate-helm-config-fields.sh
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1300 lines
55 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Helm config.json field validation script for Bifrost
# Validates that all fields from config.schema.json are properly rendered
# in the helm template output config.json
echo "🔍 Validating Helm Config JSON Fields (config.schema.json coverage)..."
echo "======================================================================"
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
TESTS_PASSED=0
TESTS_FAILED=0
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
CHART_DIR="./helm-charts/bifrost"
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
}
# Render template and extract config.json into $TMPDIR/config.json
# Usage: render_config <values-file>
render_config() {
local values_file=$1
helm template bifrost "$CHART_DIR" \
--set image.tag=v1.0.0 \
-f "$values_file" \
> "$TMPDIR/rendered.yaml" 2>"$TMPDIR/render-err.txt"
local rc=$?
if [ "$rc" -ne 0 ]; then
echo -e "${RED} Render failed:${NC}"
head -5 "$TMPDIR/render-err.txt" | sed 's/^/ /'
return 1
fi
# Extract config.json value from ConfigMap
python3 -c "
import yaml, json, sys
docs = list(yaml.safe_load_all(open('$TMPDIR/rendered.yaml')))
for doc in docs:
if doc and doc.get('kind') == 'ConfigMap' and 'config.json' in doc.get('data', {}):
cfg = json.loads(doc['data']['config.json'])
json.dump(cfg, open('$TMPDIR/config.json', 'w'), indent=2)
sys.exit(0)
print('ERROR: config.json not found in rendered ConfigMap', file=sys.stderr)
sys.exit(1)
" 2>"$TMPDIR/extract-err.txt"
}
# Assert a JSON path exists (is not null/missing) in config.json
# Usage: assert_field "test name" ".path.to.field"
assert_field() {
local test_name=$1
local jq_path=$2
local result
result=$(python3 -c "
import json, sys
cfg = json.load(open('$TMPDIR/config.json'))
parts = '$jq_path'.strip('.').split('.')
obj = cfg
for p in parts:
if p.startswith('[') and p.endswith(']'):
idx = int(p[1:-1])
if not isinstance(obj, list) or idx >= len(obj):
print('MISSING')
sys.exit(0)
obj = obj[idx]
elif isinstance(obj, dict) and p in obj:
obj = obj[p]
else:
print('MISSING')
sys.exit(0)
print('FOUND')
" 2>/dev/null)
if [ "$result" = "FOUND" ]; then
report_result "$test_name" 0
else
report_result "$test_name" 1
echo -e "${YELLOW} Expected field '$jq_path' in config.json${NC}"
fi
}
# Assert a JSON path equals a specific value
# Usage: assert_field_value "test name" ".path.to.field" "expected_value"
assert_field_value() {
local test_name=$1
local jq_path=$2
local expected=$3
local result
result=$(python3 -c "
import json, sys
cfg = json.load(open('$TMPDIR/config.json'))
parts = '$jq_path'.strip('.').split('.')
obj = cfg
for p in parts:
if p.startswith('[') and p.endswith(']'):
idx = int(p[1:-1])
if not isinstance(obj, list) or idx >= len(obj):
print('MISSING')
sys.exit(0)
obj = obj[idx]
elif isinstance(obj, dict) and p in obj:
obj = obj[p]
else:
print('MISSING')
sys.exit(0)
print(json.dumps(obj))
" 2>/dev/null)
local expected_json
expected_json=$(python3 -c "
import json
raw = '''$expected'''
if raw == 'true': print('true')
elif raw == 'false': print('false')
elif raw == 'null': print('null')
else: print(json.dumps($expected))
" 2>/dev/null || echo "\"$expected\"")
if [ "$result" = "$expected_json" ]; then
report_result "$test_name" 0
else
report_result "$test_name" 1
echo -e "${YELLOW} Expected '$jq_path' = $expected_json, got: $result${NC}"
fi
}
###############################################################################
# 1. Schema + Encryption Key + Client Config
###############################################################################
echo ""
echo -e "${CYAN}📋 1/10 - Schema, Encryption Key & Client Config${NC}"
echo "---------------------------------------------------"
cat > "$TMPDIR/values-client.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
encryptionKey: "my-secret-passphrase"
client:
dropExcessRequests: true
initialPoolSize: 500
allowedOrigins:
- "https://example.com"
enableLogging: true
disableContentLogging: true
disableDbPingsInHealth: true
logRetentionDays: 30
enforceGovernanceHeader: true
allowDirectKeys: true
maxRequestBodySizeMb: 50
compat:
convertTextToChat: true
convertChatToResponses: true
shouldDropParams: true
shouldConvertParams: true
prometheusLabels:
- "team"
- "env"
headerFilterConfig:
allowlist:
- "x-custom-header"
denylist:
- "x-blocked"
asyncJobResultTTL: 300
requiredHeaders:
- "X-Request-ID"
loggingHeaders:
- "X-Trace-ID"
allowedHeaders:
- "Authorization"
mcpAgentDepth: 5
mcpToolExecutionTimeout: 30
mcpCodeModeBindingLevel: "server"
mcpToolSyncInterval: 60
hideDeletedVirtualKeysInFilters: true
VALS
render_config "$TMPDIR/values-client.yaml"
assert_field_value 'schema field' '.$schema' '"https://www.getbifrost.ai/schema"'
assert_field_value 'encryption_key' '.encryption_key' '"my-secret-passphrase"'
assert_field_value 'client.drop_excess_requests' '.client.drop_excess_requests' 'true'
assert_field_value 'client.initial_pool_size' '.client.initial_pool_size' '500'
assert_field 'client.allowed_origins' '.client.allowed_origins'
assert_field_value 'client.enable_logging' '.client.enable_logging' 'true'
assert_field_value 'client.disable_content_logging' '.client.disable_content_logging' 'true'
assert_field_value 'client.disable_db_pings_in_health' '.client.disable_db_pings_in_health' 'true'
assert_field_value 'client.log_retention_days' '.client.log_retention_days' '30'
assert_field_value 'client.enforce_governance_header' '.client.enforce_governance_header' 'true'
assert_field_value 'client.allow_direct_keys' '.client.allow_direct_keys' 'true'
assert_field_value 'client.max_request_body_size_mb' '.client.max_request_body_size_mb' '50'
assert_field_value 'client.compat.convert_text_to_chat' '.client.compat.convert_text_to_chat' 'true'
assert_field_value 'client.compat.convert_chat_to_responses' '.client.compat.convert_chat_to_responses' 'true'
assert_field_value 'client.compat.should_drop_params' '.client.compat.should_drop_params' 'true'
assert_field_value 'client.compat.should_convert_params' '.client.compat.should_convert_params' 'true'
assert_field 'client.prometheus_labels' '.client.prometheus_labels'
assert_field 'client.header_filter_config.allowlist' '.client.header_filter_config.allowlist'
assert_field 'client.header_filter_config.denylist' '.client.header_filter_config.denylist'
# Gap 1+2: New client properties
assert_field_value 'client.async_job_result_ttl' '.client.async_job_result_ttl' '300'
assert_field 'client.required_headers' '.client.required_headers'
assert_field 'client.logging_headers' '.client.logging_headers'
assert_field 'client.allowed_headers' '.client.allowed_headers'
assert_field_value 'client.mcp_agent_depth' '.client.mcp_agent_depth' '5'
assert_field_value 'client.mcp_tool_execution_timeout' '.client.mcp_tool_execution_timeout' '30'
assert_field_value 'client.mcp_code_mode_binding_level' '.client.mcp_code_mode_binding_level' '"server"'
assert_field_value 'client.mcp_tool_sync_interval' '.client.mcp_tool_sync_interval' '60'
assert_field_value 'client.hide_deleted_virtual_keys_in_filters' '.client.hide_deleted_virtual_keys_in_filters' 'true'
###############################################################################
# 2. Framework (Pricing)
###############################################################################
echo ""
echo -e "${CYAN}💰 2/10 - Framework Config (Pricing)${NC}"
echo "--------------------------------------"
cat > "$TMPDIR/values-framework.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
framework:
pricing:
pricingUrl: "https://custom-pricing.example.com/data.json"
pricingSyncInterval: 7200
VALS
render_config "$TMPDIR/values-framework.yaml"
assert_field_value 'framework.pricing.pricing_url' '.framework.pricing.pricing_url' '"https://custom-pricing.example.com/data.json"'
assert_field_value 'framework.pricing.pricing_sync_interval' '.framework.pricing.pricing_sync_interval' '7200'
###############################################################################
# 3. Providers (standard, azure, vertex, bedrock, network/concurrency/proxy)
###############################################################################
echo ""
echo -e "${CYAN}🔑 3/10 - Provider Configurations${NC}"
echo "-----------------------------------"
cat > "$TMPDIR/values-providers.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
providers:
openai:
keys:
- name: "primary-key"
value: "sk-test"
weight: 1
models:
- "gpt-4o"
- "gpt-4o-mini"
use_for_batch_api: true
network_config:
base_url: "https://custom.openai.com"
extra_headers:
X-Custom: "value"
default_request_timeout_in_seconds: 120
max_retries: 5
retry_backoff_initial_ms: 200
retry_backoff_max_ms: 10000
concurrency_and_buffer_size:
concurrency: 50
buffer_size: 100
proxy_config:
type: "http"
url: "http://proxy:3128"
username: "user"
password: "pass"
ca_cert_pem: "PEM_DATA"
send_back_raw_response: true
azure:
keys:
- name: "azure-key"
value: "az-test"
weight: 1
azure_key_config:
endpoint: "https://myresource.openai.azure.com"
api_version: "2024-02-15-preview"
deployments:
gpt-4o: "my-deployment"
vertex:
keys:
- name: "vertex-key"
value: ""
weight: 1
vertex_key_config:
project_id: "my-project"
region: "us-central1"
auth_credentials: "env.GOOGLE_CREDS"
bedrock:
keys:
- name: "bedrock-key"
value: ""
weight: 1
bedrock_key_config:
region: "us-east-1"
access_key: "env.AWS_KEY"
secret_key: "env.AWS_SECRET"
session_token: "env.AWS_TOKEN"
arn: "arn:aws:iam::role/test"
VALS
render_config "$TMPDIR/values-providers.yaml"
# Standard provider keys
assert_field_value 'providers.openai.keys[0].name' '.providers.openai.keys.[0].name' '"primary-key"'
assert_field_value 'providers.openai.keys[0].value' '.providers.openai.keys.[0].value' '"sk-test"'
assert_field_value 'providers.openai.keys[0].weight' '.providers.openai.keys.[0].weight' '1'
assert_field 'providers.openai.keys[0].models' '.providers.openai.keys.[0].models'
assert_field_value 'providers.openai.keys[0].use_for_batch_api' '.providers.openai.keys.[0].use_for_batch_api' 'true'
# Network config
assert_field_value 'providers.openai.network_config.base_url' '.providers.openai.network_config.base_url' '"https://custom.openai.com"'
assert_field 'providers.openai.network_config.extra_headers' '.providers.openai.network_config.extra_headers'
assert_field_value 'providers.openai.network_config.default_request_timeout_in_seconds' '.providers.openai.network_config.default_request_timeout_in_seconds' '120'
assert_field_value 'providers.openai.network_config.max_retries' '.providers.openai.network_config.max_retries' '5'
assert_field_value 'providers.openai.network_config.retry_backoff_initial_ms' '.providers.openai.network_config.retry_backoff_initial_ms' '200'
assert_field_value 'providers.openai.network_config.retry_backoff_max_ms' '.providers.openai.network_config.retry_backoff_max_ms' '10000'
# Concurrency config
assert_field_value 'providers.openai.concurrency_and_buffer_size.concurrency' '.providers.openai.concurrency_and_buffer_size.concurrency' '50'
assert_field_value 'providers.openai.concurrency_and_buffer_size.buffer_size' '.providers.openai.concurrency_and_buffer_size.buffer_size' '100'
# Proxy config
assert_field_value 'providers.openai.proxy_config.type' '.providers.openai.proxy_config.type' '"http"'
assert_field_value 'providers.openai.proxy_config.url' '.providers.openai.proxy_config.url' '"http://proxy:3128"'
assert_field_value 'providers.openai.proxy_config.username' '.providers.openai.proxy_config.username' '"user"'
assert_field_value 'providers.openai.proxy_config.password' '.providers.openai.proxy_config.password' '"pass"'
assert_field_value 'providers.openai.proxy_config.ca_cert_pem' '.providers.openai.proxy_config.ca_cert_pem' '"PEM_DATA"'
# send_back_raw_response
assert_field_value 'providers.openai.send_back_raw_response' '.providers.openai.send_back_raw_response' 'true'
# Azure key config
assert_field_value 'providers.azure.keys[0].azure_key_config.endpoint' '.providers.azure.keys.[0].azure_key_config.endpoint' '"https://myresource.openai.azure.com"'
assert_field_value 'providers.azure.keys[0].azure_key_config.api_version' '.providers.azure.keys.[0].azure_key_config.api_version' '"2024-02-15-preview"'
assert_field 'providers.azure.keys[0].azure_key_config.deployments' '.providers.azure.keys.[0].azure_key_config.deployments'
# Vertex key config
assert_field_value 'providers.vertex.keys[0].vertex_key_config.project_id' '.providers.vertex.keys.[0].vertex_key_config.project_id' '"my-project"'
assert_field_value 'providers.vertex.keys[0].vertex_key_config.region' '.providers.vertex.keys.[0].vertex_key_config.region' '"us-central1"'
assert_field_value 'providers.vertex.keys[0].vertex_key_config.auth_credentials' '.providers.vertex.keys.[0].vertex_key_config.auth_credentials' '"env.GOOGLE_CREDS"'
# Bedrock key config
assert_field_value 'providers.bedrock.keys[0].bedrock_key_config.region' '.providers.bedrock.keys.[0].bedrock_key_config.region' '"us-east-1"'
assert_field_value 'providers.bedrock.keys[0].bedrock_key_config.access_key' '.providers.bedrock.keys.[0].bedrock_key_config.access_key' '"env.AWS_KEY"'
assert_field_value 'providers.bedrock.keys[0].bedrock_key_config.secret_key' '.providers.bedrock.keys.[0].bedrock_key_config.secret_key' '"env.AWS_SECRET"'
assert_field_value 'providers.bedrock.keys[0].bedrock_key_config.session_token' '.providers.bedrock.keys.[0].bedrock_key_config.session_token' '"env.AWS_TOKEN"'
assert_field_value 'providers.bedrock.keys[0].bedrock_key_config.arn' '.providers.bedrock.keys.[0].bedrock_key_config.arn' '"arn:aws:iam::role/test"'
###############################################################################
# 4. Governance (budgets, rate_limits, customers, teams, virtual_keys, routing_rules, auth_config)
###############################################################################
echo ""
echo -e "${CYAN}🏛️ 4/10 - Governance Configuration${NC}"
echo "-------------------------------------"
cat > "$TMPDIR/values-governance.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
governance:
budgets:
- id: "budget-1"
max_limit: 100
reset_duration: "1M"
rateLimits:
- id: "rl-1"
token_max_limit: 50000
token_reset_duration: "1d"
request_max_limit: 1000
request_reset_duration: "1h"
customers:
- id: "cust-1"
name: "Acme Corp"
budget_id: "budget-1"
rate_limit_id: "rl-1"
teams:
- id: "team-1"
name: "Engineering"
customer_id: "cust-1"
budget_id: "budget-1"
rate_limit_id: "rl-1"
profile:
department: "eng"
config:
max_tokens: 4096
claims:
role: "admin"
virtualKeys:
- id: "vk-1"
name: "Test VK"
value: "vk-test-value"
description: "A test virtual key"
is_active: true
team_id: "team-1"
customer_id: "cust-1"
budget_id: "budget-1"
rate_limit_id: "rl-1"
provider_configs:
- provider: "openai"
weight: 1.0
allowed_models:
- "gpt-4o"
budget_id: "budget-1"
rate_limit_id: "rl-1"
keys:
- key_id: "key-uuid-1"
name: "vk-provider-key"
value: "sk-test"
mcp_configs:
- mcp_client_id: 1
tools_to_execute:
- "search"
- "compute"
modelConfigs:
- id: "mc-1"
model_name: "gpt-4o"
provider: "openai"
budget_id: "budget-1"
rate_limit_id: "rl-1"
providers:
- name: "openai"
budget_id: "budget-1"
rate_limit_id: "rl-1"
send_back_raw_request: false
send_back_raw_response: false
routingRules:
- id: "route-1"
name: "Route GPT to Azure"
description: "Redirect GPT models to Azure"
enabled: true
cel_expression: "request.model.startsWith('gpt-')"
targets:
- provider: "azure"
model: "gpt-4o"
weight: 0.8
- provider: "openai"
model: "gpt-4o"
weight: 0.2
scope: "global"
priority: 10
- id: "route-2"
name: "Team-scoped route"
enabled: true
cel_expression: "true"
targets:
- provider: "openai"
weight: 1
scope: "team"
scope_id: "team-1"
priority: 0
authConfig:
adminUsername: "admin"
adminPassword: "secret"
isEnabled: true
disableAuthOnInference: true
VALS
render_config "$TMPDIR/values-governance.yaml"
# Budgets
assert_field_value 'governance.budgets[0].id' '.governance.budgets.[0].id' '"budget-1"'
assert_field_value 'governance.budgets[0].max_limit' '.governance.budgets.[0].max_limit' '100'
assert_field_value 'governance.budgets[0].reset_duration' '.governance.budgets.[0].reset_duration' '"1M"'
# Rate limits
assert_field_value 'governance.rate_limits[0].id' '.governance.rate_limits.[0].id' '"rl-1"'
assert_field_value 'governance.rate_limits[0].token_max_limit' '.governance.rate_limits.[0].token_max_limit' '50000'
assert_field_value 'governance.rate_limits[0].token_reset_duration' '.governance.rate_limits.[0].token_reset_duration' '"1d"'
assert_field_value 'governance.rate_limits[0].request_max_limit' '.governance.rate_limits.[0].request_max_limit' '1000'
assert_field_value 'governance.rate_limits[0].request_reset_duration' '.governance.rate_limits.[0].request_reset_duration' '"1h"'
# Customers
assert_field_value 'governance.customers[0].id' '.governance.customers.[0].id' '"cust-1"'
assert_field_value 'governance.customers[0].name' '.governance.customers.[0].name' '"Acme Corp"'
assert_field_value 'governance.customers[0].budget_id' '.governance.customers.[0].budget_id' '"budget-1"'
assert_field_value 'governance.customers[0].rate_limit_id' '.governance.customers.[0].rate_limit_id' '"rl-1"'
# Teams
assert_field_value 'governance.teams[0].id' '.governance.teams.[0].id' '"team-1"'
assert_field_value 'governance.teams[0].name' '.governance.teams.[0].name' '"Engineering"'
assert_field_value 'governance.teams[0].customer_id' '.governance.teams.[0].customer_id' '"cust-1"'
assert_field_value 'governance.teams[0].budget_id' '.governance.teams.[0].budget_id' '"budget-1"'
assert_field_value 'governance.teams[0].rate_limit_id' '.governance.teams.[0].rate_limit_id' '"rl-1"'
assert_field 'governance.teams[0].profile' '.governance.teams.[0].profile'
assert_field 'governance.teams[0].config' '.governance.teams.[0].config'
assert_field 'governance.teams[0].claims' '.governance.teams.[0].claims'
# Virtual keys
assert_field_value 'governance.virtual_keys[0].id' '.governance.virtual_keys.[0].id' '"vk-1"'
assert_field_value 'governance.virtual_keys[0].name' '.governance.virtual_keys.[0].name' '"Test VK"'
assert_field_value 'governance.virtual_keys[0].value' '.governance.virtual_keys.[0].value' '"vk-test-value"'
assert_field_value 'governance.virtual_keys[0].description' '.governance.virtual_keys.[0].description' '"A test virtual key"'
assert_field_value 'governance.virtual_keys[0].is_active' '.governance.virtual_keys.[0].is_active' 'true'
assert_field_value 'governance.virtual_keys[0].team_id' '.governance.virtual_keys.[0].team_id' '"team-1"'
assert_field_value 'governance.virtual_keys[0].customer_id' '.governance.virtual_keys.[0].customer_id' '"cust-1"'
assert_field_value 'governance.virtual_keys[0].budget_id' '.governance.virtual_keys.[0].budget_id' '"budget-1"'
assert_field_value 'governance.virtual_keys[0].rate_limit_id' '.governance.virtual_keys.[0].rate_limit_id' '"rl-1"'
assert_field 'governance.virtual_keys[0].provider_configs' '.governance.virtual_keys.[0].provider_configs'
assert_field_value 'governance.virtual_keys[0].provider_configs[0].provider' '.governance.virtual_keys.[0].provider_configs.[0].provider' '"openai"'
assert_field 'governance.virtual_keys[0].provider_configs[0].allowed_models' '.governance.virtual_keys.[0].provider_configs.[0].allowed_models'
assert_field 'governance.virtual_keys[0].provider_configs[0].keys' '.governance.virtual_keys.[0].provider_configs.[0].keys'
assert_field 'governance.virtual_keys[0].mcp_configs' '.governance.virtual_keys.[0].mcp_configs'
assert_field_value 'governance.virtual_keys[0].mcp_configs[0].mcp_client_id' '.governance.virtual_keys.[0].mcp_configs.[0].mcp_client_id' '1'
assert_field 'governance.virtual_keys[0].mcp_configs[0].tools_to_execute' '.governance.virtual_keys.[0].mcp_configs.[0].tools_to_execute'
# Routing rules
assert_field_value 'governance.routing_rules[0].id' '.governance.routing_rules.[0].id' '"route-1"'
assert_field_value 'governance.routing_rules[0].name' '.governance.routing_rules.[0].name' '"Route GPT to Azure"'
assert_field_value 'governance.routing_rules[0].description' '.governance.routing_rules.[0].description' '"Redirect GPT models to Azure"'
assert_field_value 'governance.routing_rules[0].enabled' '.governance.routing_rules.[0].enabled' 'true'
assert_field_value 'governance.routing_rules[0].cel_expression' '.governance.routing_rules.[0].cel_expression' '"request.model.startsWith('\''gpt-'\'')"'
assert_field 'governance.routing_rules[0].targets' '.governance.routing_rules.[0].targets'
assert_field_value 'governance.routing_rules[0].targets[0].provider' '.governance.routing_rules.[0].targets.[0].provider' '"azure"'
assert_field_value 'governance.routing_rules[0].targets[0].model' '.governance.routing_rules.[0].targets.[0].model' '"gpt-4o"'
assert_field_value 'governance.routing_rules[0].targets[0].weight' '.governance.routing_rules.[0].targets.[0].weight' '0.8'
assert_field_value 'governance.routing_rules[0].targets[1].provider' '.governance.routing_rules.[0].targets.[1].provider' '"openai"'
assert_field_value 'governance.routing_rules[0].targets[1].weight' '.governance.routing_rules.[0].targets.[1].weight' '0.2'
assert_field_value 'governance.routing_rules[0].scope' '.governance.routing_rules.[0].scope' '"global"'
assert_field_value 'governance.routing_rules[0].priority' '.governance.routing_rules.[0].priority' '10'
assert_field_value 'governance.routing_rules[1].scope' '.governance.routing_rules.[1].scope' '"team"'
assert_field_value 'governance.routing_rules[1].scope_id' '.governance.routing_rules.[1].scope_id' '"team-1"'
assert_field 'governance.routing_rules[1].targets' '.governance.routing_rules.[1].targets'
# Model configs (Gap 5a)
assert_field 'governance.model_configs' '.governance.model_configs'
assert_field_value 'governance.model_configs[0].id' '.governance.model_configs.[0].id' '"mc-1"'
assert_field_value 'governance.model_configs[0].model_name' '.governance.model_configs.[0].model_name' '"gpt-4o"'
assert_field_value 'governance.model_configs[0].provider' '.governance.model_configs.[0].provider' '"openai"'
assert_field_value 'governance.model_configs[0].budget_id' '.governance.model_configs.[0].budget_id' '"budget-1"'
assert_field_value 'governance.model_configs[0].rate_limit_id' '.governance.model_configs.[0].rate_limit_id' '"rl-1"'
# Providers (Gap 5b)
assert_field 'governance.providers' '.governance.providers'
assert_field_value 'governance.providers[0].name' '.governance.providers.[0].name' '"openai"'
assert_field_value 'governance.providers[0].budget_id' '.governance.providers.[0].budget_id' '"budget-1"'
assert_field_value 'governance.providers[0].rate_limit_id' '.governance.providers.[0].rate_limit_id' '"rl-1"'
# Auth config
assert_field_value 'governance.auth_config.admin_username' '.governance.auth_config.admin_username' '"admin"'
assert_field_value 'governance.auth_config.admin_password' '.governance.auth_config.admin_password' '"secret"'
assert_field_value 'governance.auth_config.is_enabled' '.governance.auth_config.is_enabled' 'true'
assert_field_value 'governance.auth_config.disable_auth_on_inference' '.governance.auth_config.disable_auth_on_inference' 'true'
###############################################################################
# 5. Top-level Auth Config
###############################################################################
echo ""
echo -e "${CYAN}🔐 5/10 - Top-level Auth Config${NC}"
echo "--------------------------------"
cat > "$TMPDIR/values-auth.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
authConfig:
adminUsername: "root"
adminPassword: "rootpass"
isEnabled: true
disableAuthOnInference: false
VALS
render_config "$TMPDIR/values-auth.yaml"
assert_field_value 'auth_config.admin_username' '.auth_config.admin_username' '"root"'
assert_field_value 'auth_config.admin_password' '.auth_config.admin_password' '"rootpass"'
assert_field_value 'auth_config.is_enabled' '.auth_config.is_enabled' 'true'
assert_field_value 'auth_config.disable_auth_on_inference' '.auth_config.disable_auth_on_inference' 'false'
###############################################################################
# 6. Plugins (telemetry, logging, governance, maxim, semantic_cache, otel, datadog, custom)
###############################################################################
echo ""
echo -e "${CYAN}🔌 6/10 - Plugins Configuration${NC}"
echo "--------------------------------"
cat > "$TMPDIR/values-plugins.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
plugins:
telemetry:
enabled: true
config:
push_gateway:
enabled: true
push_gateway_url: "http://pushgateway:9091"
job_name: "bifrost-test"
instance_id: "node-1"
push_interval: 30
basic_auth:
username: "prom"
password: "prompass"
logging:
enabled: true
config: {}
governance:
enabled: true
config:
is_vk_mandatory: true
required_headers:
- "X-Team-ID"
is_enterprise: true
maxim:
enabled: true
config:
api_key: "maxim-key-123"
log_repo_id: "repo-456"
secretRef:
name: ""
key: "api-key"
semanticCache:
enabled: true
config:
provider: "openai"
keys:
- "sk-embed-key"
embedding_model: "text-embedding-3-small"
dimension: 1536
threshold: 0.85
ttl: "10m"
conversation_history_threshold: 5
cache_by_model: true
cache_by_provider: false
exclude_system_prompt: true
cleanup_on_shutdown: true
vector_store_namespace: "bifrost-cache"
otel:
enabled: true
config:
service_name: "bifrost-test"
collector_url: "otel-collector:4317"
trace_type: "genai_extension"
protocol: "grpc"
metrics_enabled: true
metrics_endpoint: "otel-collector:4317"
metrics_push_interval: 30
headers:
Authorization: "Bearer token"
tls_ca_cert: "/certs/ca.pem"
insecure: true
datadog:
enabled: true
config:
service_name: "bifrost-dd"
agent_addr: "dd-agent:8126"
env: "staging"
version: "1.0.0"
custom_tags:
team: "platform"
enable_traces: true
custom:
- name: "my-plugin"
enabled: true
path: "/plugins/my-plugin.so"
version: 2
config:
key1: "val1"
vectorStore:
enabled: true
type: weaviate
weaviate:
enabled: true
VALS
render_config "$TMPDIR/values-plugins.yaml"
# Telemetry plugin
assert_field_value 'plugins: telemetry name' '.plugins.[0].name' '"telemetry"'
assert_field_value 'plugins: telemetry enabled' '.plugins.[0].enabled' 'true'
assert_field 'plugins: telemetry push_gateway' '.plugins.[0].config.push_gateway'
# Logging plugin
assert_field_value 'plugins: logging name' '.plugins.[1].name' '"logging"'
# Governance plugin
assert_field_value 'plugins: governance name' '.plugins.[2].name' '"governance"'
assert_field_value 'plugins: governance is_vk_mandatory' '.plugins.[2].config.is_vk_mandatory' 'true'
assert_field 'plugins: governance required_headers' '.plugins.[2].config.required_headers'
assert_field_value 'plugins: governance is_enterprise' '.plugins.[2].config.is_enterprise' 'true'
# Maxim plugin
assert_field_value 'plugins: maxim name' '.plugins.[3].name' '"maxim"'
assert_field_value 'plugins: maxim api_key' '.plugins.[3].config.api_key' '"maxim-key-123"'
assert_field_value 'plugins: maxim log_repo_id' '.plugins.[3].config.log_repo_id' '"repo-456"'
# Semantic cache plugin
assert_field_value 'plugins: semantic_cache name' '.plugins.[4].name' '"semantic_cache"'
assert_field_value 'plugins: semantic_cache provider' '.plugins.[4].config.provider' '"openai"'
assert_field 'plugins: semantic_cache keys' '.plugins.[4].config.keys'
assert_field_value 'plugins: semantic_cache embedding_model' '.plugins.[4].config.embedding_model' '"text-embedding-3-small"'
assert_field_value 'plugins: semantic_cache dimension' '.plugins.[4].config.dimension' '1536'
assert_field_value 'plugins: semantic_cache threshold' '.plugins.[4].config.threshold' '0.85'
assert_field_value 'plugins: semantic_cache ttl' '.plugins.[4].config.ttl' '"10m"'
assert_field_value 'plugins: semantic_cache conversation_history_threshold' '.plugins.[4].config.conversation_history_threshold' '5'
assert_field_value 'plugins: semantic_cache cache_by_model' '.plugins.[4].config.cache_by_model' 'true'
assert_field_value 'plugins: semantic_cache cache_by_provider' '.plugins.[4].config.cache_by_provider' 'false'
assert_field_value 'plugins: semantic_cache exclude_system_prompt' '.plugins.[4].config.exclude_system_prompt' 'true'
assert_field_value 'plugins: semantic_cache cleanup_on_shutdown' '.plugins.[4].config.cleanup_on_shutdown' 'true'
assert_field_value 'plugins: semantic_cache vector_store_namespace' '.plugins.[4].config.vector_store_namespace' '"bifrost-cache"'
# OTEL plugin
assert_field_value 'plugins: otel name' '.plugins.[5].name' '"otel"'
assert_field_value 'plugins: otel service_name' '.plugins.[5].config.service_name' '"bifrost-test"'
assert_field_value 'plugins: otel collector_url' '.plugins.[5].config.collector_url' '"otel-collector:4317"'
assert_field_value 'plugins: otel trace_type' '.plugins.[5].config.trace_type' '"genai_extension"'
assert_field_value 'plugins: otel protocol' '.plugins.[5].config.protocol' '"grpc"'
assert_field_value 'plugins: otel metrics_enabled' '.plugins.[5].config.metrics_enabled' 'true'
assert_field_value 'plugins: otel metrics_endpoint' '.plugins.[5].config.metrics_endpoint' '"otel-collector:4317"'
assert_field_value 'plugins: otel metrics_push_interval' '.plugins.[5].config.metrics_push_interval' '30'
assert_field 'plugins: otel headers' '.plugins.[5].config.headers'
assert_field_value 'plugins: otel tls_ca_cert' '.plugins.[5].config.tls_ca_cert' '"/certs/ca.pem"'
assert_field_value 'plugins: otel insecure' '.plugins.[5].config.insecure' 'true'
# Datadog plugin
assert_field_value 'plugins: datadog name' '.plugins.[6].name' '"datadog"'
assert_field_value 'plugins: datadog service_name' '.plugins.[6].config.service_name' '"bifrost-dd"'
assert_field_value 'plugins: datadog agent_addr' '.plugins.[6].config.agent_addr' '"dd-agent:8126"'
assert_field_value 'plugins: datadog env' '.plugins.[6].config.env' '"staging"'
assert_field_value 'plugins: datadog version' '.plugins.[6].config.version' '"1.0.0"'
assert_field 'plugins: datadog custom_tags' '.plugins.[6].config.custom_tags'
assert_field_value 'plugins: datadog enable_traces' '.plugins.[6].config.enable_traces' 'true'
# Custom plugin
assert_field_value 'plugins: custom name' '.plugins.[7].name' '"my-plugin"'
assert_field_value 'plugins: custom path' '.plugins.[7].path' '"/plugins/my-plugin.so"'
assert_field_value 'plugins: custom version' '.plugins.[7].version' '2'
assert_field 'plugins: custom config' '.plugins.[7].config'
###############################################################################
# 7. MCP Configuration
###############################################################################
echo ""
echo -e "${CYAN}🔧 7/10 - MCP Configuration${NC}"
echo "-----------------------------"
cat > "$TMPDIR/values-mcp.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
mcp:
enabled: true
toolSyncInterval: "10m"
clientConfigs:
- name: "stdio-server"
connectionType: "stdio"
clientId: "client-1"
isCodeModeClient: true
toolSyncInterval: "5m"
isPingAvailable: false
toolPricing:
search: 0.05
stdioConfig:
command: "/usr/bin/mcp-server"
args:
- "--port"
- "3000"
envs:
- "MCP_TOKEN=abc"
- name: "http-server"
connectionType: "http"
httpConfig:
url: "https://mcp.example.com/v1"
- name: "ws-server"
connectionType: "websocket"
websocketConfig:
url: "wss://mcp.example.com/ws"
toolManagerConfig:
toolExecutionTimeout: 60
maxAgentDepth: 5
codeModeBindingLevel: "server"
VALS
render_config "$TMPDIR/values-mcp.yaml"
assert_field 'mcp.client_configs' '.mcp.client_configs'
# stdio client
assert_field_value 'mcp client[0] name' '.mcp.client_configs.[0].name' '"stdio-server"'
assert_field_value 'mcp client[0] connection_type' '.mcp.client_configs.[0].connection_type' '"stdio"'
assert_field_value 'mcp client[0] stdio_config.command' '.mcp.client_configs.[0].stdio_config.command' '"/usr/bin/mcp-server"'
assert_field 'mcp client[0] stdio_config.args' '.mcp.client_configs.[0].stdio_config.args'
assert_field 'mcp client[0] stdio_config.envs' '.mcp.client_configs.[0].stdio_config.envs'
# http client (mapped to connection_string)
assert_field_value 'mcp client[1] name' '.mcp.client_configs.[1].name' '"http-server"'
assert_field_value 'mcp client[1] connection_type' '.mcp.client_configs.[1].connection_type' '"http"'
assert_field_value 'mcp client[1] connection_string' '.mcp.client_configs.[1].connection_string' '"https://mcp.example.com/v1"'
# websocket client (mapped to sse + connection_string)
assert_field_value 'mcp client[2] name' '.mcp.client_configs.[2].name' '"ws-server"'
assert_field_value 'mcp client[2] connection_type (ws->sse)' '.mcp.client_configs.[2].connection_type' '"sse"'
assert_field_value 'mcp client[2] connection_string' '.mcp.client_configs.[2].connection_string' '"wss://mcp.example.com/ws"'
# Tool manager config
assert_field_value 'mcp tool_manager_config.tool_execution_timeout' '.mcp.tool_manager_config.tool_execution_timeout' '60'
assert_field_value 'mcp tool_manager_config.max_agent_depth' '.mcp.tool_manager_config.max_agent_depth' '5'
# Gap 6a: Global tool sync interval
assert_field_value 'mcp tool_sync_interval' '.mcp.tool_sync_interval' '"10m"'
# Gap 6b: Per-client new fields
assert_field_value 'mcp client[0] client_id' '.mcp.client_configs.[0].client_id' '"client-1"'
assert_field_value 'mcp client[0] is_code_mode_client' '.mcp.client_configs.[0].is_code_mode_client' 'true'
assert_field_value 'mcp client[0] tool_sync_interval' '.mcp.client_configs.[0].tool_sync_interval' '"5m"'
assert_field_value 'mcp client[0] is_ping_available' '.mcp.client_configs.[0].is_ping_available' 'false'
assert_field 'mcp client[0] tool_pricing' '.mcp.client_configs.[0].tool_pricing'
assert_field_value 'mcp client[0] tool_pricing.search' '.mcp.client_configs.[0].tool_pricing.search' '0.05'
# Gap 6c: Tool manager codeModeBindingLevel
assert_field_value 'mcp tool_manager_config.code_mode_binding_level' '.mcp.tool_manager_config.code_mode_binding_level' '"server"'
###############################################################################
# 8. Cluster, SCIM, Load Balancer, Guardrails, Audit Logs
###############################################################################
echo ""
echo -e "${CYAN}🌐 8/10 - Cluster, SCIM, LB, Guardrails, Audit Logs${NC}"
echo "-----------------------------------------------------"
cat > "$TMPDIR/values-cluster.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
cluster:
enabled: true
region: "us-east-1"
peers:
- "bifrost-0.bifrost-headless:7946"
- "bifrost-1.bifrost-headless:7946"
gossip:
port: 7946
config:
timeoutSeconds: 10
successThreshold: 3
failureThreshold: 3
discovery:
enabled: true
type: "kubernetes"
allowedAddressSpace:
- "10.0.0.0/8"
k8sNamespace: "bifrost"
k8sLabelSelector: "app=bifrost"
VALS
render_config "$TMPDIR/values-cluster.yaml"
assert_field_value 'cluster_config.enabled' '.cluster_config.enabled' 'true'
assert_field 'cluster_config.peers' '.cluster_config.peers'
assert_field_value 'cluster_config.gossip.port' '.cluster_config.gossip.port' '7946'
assert_field_value 'cluster_config.gossip.config.timeout_seconds' '.cluster_config.gossip.config.timeout_seconds' '10'
assert_field_value 'cluster_config.gossip.config.success_threshold' '.cluster_config.gossip.config.success_threshold' '3'
assert_field_value 'cluster_config.gossip.config.failure_threshold' '.cluster_config.gossip.config.failure_threshold' '3'
assert_field_value 'cluster_config.discovery.enabled' '.cluster_config.discovery.enabled' 'true'
assert_field_value 'cluster_config.discovery.type' '.cluster_config.discovery.type' '"kubernetes"'
assert_field 'cluster_config.discovery.allowed_address_space' '.cluster_config.discovery.allowed_address_space'
assert_field_value 'cluster_config.discovery.k8s_namespace' '.cluster_config.discovery.k8s_namespace' '"bifrost"'
assert_field_value 'cluster_config.discovery.k8s_label_selector' '.cluster_config.discovery.k8s_label_selector' '"app=bifrost"'
# Gap 7: Cluster region
assert_field_value 'cluster_config.region' '.cluster_config.region' '"us-east-1"'
# SCIM - Okta
cat > "$TMPDIR/values-scim-okta.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
scim:
enabled: true
provider: "okta"
config:
issuerUrl: "https://dev-123.okta.com/oauth2/default"
clientId: "okta-client-id"
clientSecret: "okta-client-secret"
apiToken: "okta-api-token"
audience: "api://default"
userIdField: "sub"
teamIdsField: "groups"
rolesField: "roles"
VALS
render_config "$TMPDIR/values-scim-okta.yaml"
assert_field_value 'scim_config.enabled' '.scim_config.enabled' 'true'
assert_field_value 'scim_config.provider' '.scim_config.provider' '"okta"'
assert_field 'scim_config.config' '.scim_config.config'
assert_field 'scim_config.config.apiToken' '.scim_config.config.apiToken'
assert_field 'scim_config.config.clientSecret' '.scim_config.config.clientSecret'
# SCIM - Entra
cat > "$TMPDIR/values-scim-entra.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
scim:
enabled: true
provider: "entra"
config:
tenantId: "tenant-uuid"
clientId: "entra-client-id"
clientSecret: "entra-secret"
audience: "api://entra"
appIdUri: "api://entra-client-id"
userIdField: "oid"
teamIdsField: "groups"
rolesField: "roles"
VALS
render_config "$TMPDIR/values-scim-entra.yaml"
assert_field_value 'scim_config (entra) provider' '.scim_config.provider' '"entra"'
assert_field 'scim_config (entra) config' '.scim_config.config'
assert_field_value 'scim_config (entra) enabled' '.scim_config.enabled' 'true'
assert_field 'scim_config (entra) config.tenantId' '.scim_config.config.tenantId'
assert_field 'scim_config (entra) config.clientId' '.scim_config.config.clientId'
# Load Balancer
cat > "$TMPDIR/values-lb.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
loadBalancer:
enabled: true
trackerConfig:
window_size: 100
bootstrap:
route_metrics: {}
direction_metrics: {}
routes: {}
VALS
render_config "$TMPDIR/values-lb.yaml"
assert_field_value 'load_balancer_config.enabled' '.load_balancer_config.enabled' 'true'
assert_field 'load_balancer_config.tracker_config' '.load_balancer_config.tracker_config'
assert_field 'load_balancer_config.bootstrap' '.load_balancer_config.bootstrap'
# Guardrails
cat > "$TMPDIR/values-guardrails.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
guardrails:
rules:
- id: 1
name: "Block PII"
description: "Block PII in requests"
enabled: true
cel_expression: "!contains(request.body, 'SSN')"
apply_to: "input"
sampling_rate: 100
timeout: 1000
provider_config_ids:
- 1
providers:
- id: 1
provider_name: "bedrock"
policy_name: "content-filter"
enabled: true
timeout: 5000
config:
guardrailId: "abc"
VALS
render_config "$TMPDIR/values-guardrails.yaml"
assert_field 'guardrails_config.guardrail_rules' '.guardrails_config.guardrail_rules'
assert_field_value 'guardrails rule[0].id' '.guardrails_config.guardrail_rules.[0].id' '1'
assert_field_value 'guardrails rule[0].name' '.guardrails_config.guardrail_rules.[0].name' '"Block PII"'
assert_field_value 'guardrails rule[0].description' '.guardrails_config.guardrail_rules.[0].description' '"Block PII in requests"'
assert_field_value 'guardrails rule[0].enabled' '.guardrails_config.guardrail_rules.[0].enabled' 'true'
assert_field_value 'guardrails rule[0].apply_to' '.guardrails_config.guardrail_rules.[0].apply_to' '"input"'
assert_field_value 'guardrails rule[0].sampling_rate' '.guardrails_config.guardrail_rules.[0].sampling_rate' '100'
assert_field_value 'guardrails rule[0].timeout' '.guardrails_config.guardrail_rules.[0].timeout' '1000'
assert_field 'guardrails rule[0].provider_config_ids' '.guardrails_config.guardrail_rules.[0].provider_config_ids'
assert_field 'guardrails_config.guardrail_providers' '.guardrails_config.guardrail_providers'
assert_field_value 'guardrails provider[0].id' '.guardrails_config.guardrail_providers.[0].id' '1'
assert_field_value 'guardrails provider[0].provider_name' '.guardrails_config.guardrail_providers.[0].provider_name' '"bedrock"'
assert_field_value 'guardrails provider[0].policy_name' '.guardrails_config.guardrail_providers.[0].policy_name' '"content-filter"'
assert_field_value 'guardrails provider[0].enabled' '.guardrails_config.guardrail_providers.[0].enabled' 'true'
assert_field_value 'guardrails provider[0].timeout' '.guardrails_config.guardrail_providers.[0].timeout' '5000'
assert_field 'guardrails provider[0].config' '.guardrails_config.guardrail_providers.[0].config'
# Audit Logs
cat > "$TMPDIR/values-audit.yaml" << 'VALS'
image:
tag: v1.0.0
bifrost:
auditLogs:
disabled: false
hmacKey: "my-hmac-secret-key-32-bytes-long!"
VALS
render_config "$TMPDIR/values-audit.yaml"
assert_field_value 'audit_logs.disabled' '.audit_logs.disabled' 'false'
assert_field_value 'audit_logs.hmac_key' '.audit_logs.hmac_key' '"my-hmac-secret-key-32-bytes-long!"'
###############################################################################
# 9. Vector Store Types (weaviate, redis, qdrant, pinecone) with all fields
###############################################################################
echo ""
echo -e "${CYAN}🗄️ 9/10 - Vector Store Config Fields${NC}"
echo "--------------------------------------"
# Weaviate with all fields
cat > "$TMPDIR/values-vs-weaviate.yaml" << 'VALS'
image:
tag: v1.0.0
vectorStore:
enabled: true
type: weaviate
weaviate:
external:
enabled: true
scheme: https
host: "weaviate.example.com:443"
apiKey: "wv-api-key"
grpcHost: "weaviate-grpc.example.com:443"
grpcSecured: true
timeout: "10s"
className: "BifrostCache"
VALS
render_config "$TMPDIR/values-vs-weaviate.yaml"
assert_field_value 'vector_store.type (weaviate)' '.vector_store.type' '"weaviate"'
assert_field_value 'vector_store.enabled (weaviate)' '.vector_store.enabled' 'true'
assert_field_value 'weaviate config.scheme' '.vector_store.config.scheme' '"https"'
assert_field_value 'weaviate config.host' '.vector_store.config.host' '"weaviate.example.com:443"'
assert_field_value 'weaviate config.api_key' '.vector_store.config.api_key' '"wv-api-key"'
assert_field_value 'weaviate config.grpc_config.host' '.vector_store.config.grpc_config.host' '"weaviate-grpc.example.com:443"'
assert_field_value 'weaviate config.grpc_config.secured' '.vector_store.config.grpc_config.secured' 'true'
assert_field_value 'weaviate config.timeout' '.vector_store.config.timeout' '"10s"'
assert_field_value 'weaviate config.class_name' '.vector_store.config.class_name' '"BifrostCache"'
# Redis with all fields
cat > "$TMPDIR/values-vs-redis.yaml" << 'VALS'
image:
tag: v1.0.0
vectorStore:
enabled: true
type: redis
redis:
external:
enabled: true
host: "redis.example.com"
port: 6380
username: "redisuser"
password: "redispass"
database: 3
poolSize: 50
maxActiveConns: 100
minIdleConns: 5
maxIdleConns: 20
connMaxLifetime: "30m"
connMaxIdleTime: "5m"
dialTimeout: "5s"
readTimeout: "3s"
writeTimeout: "3s"
contextTimeout: "10s"
VALS
render_config "$TMPDIR/values-vs-redis.yaml"
assert_field_value 'vector_store.type (redis)' '.vector_store.type' '"redis"'
assert_field_value 'redis config.addr' '.vector_store.config.addr' '"redis.example.com:6380"'
assert_field_value 'redis config.username' '.vector_store.config.username' '"redisuser"'
assert_field_value 'redis config.password' '.vector_store.config.password' '"redispass"'
assert_field_value 'redis config.db' '.vector_store.config.db' '3'
assert_field_value 'redis config.pool_size' '.vector_store.config.pool_size' '50'
assert_field_value 'redis config.max_active_conns' '.vector_store.config.max_active_conns' '100'
assert_field_value 'redis config.min_idle_conns' '.vector_store.config.min_idle_conns' '5'
assert_field_value 'redis config.max_idle_conns' '.vector_store.config.max_idle_conns' '20'
assert_field_value 'redis config.conn_max_lifetime' '.vector_store.config.conn_max_lifetime' '"30m"'
assert_field_value 'redis config.conn_max_idle_time' '.vector_store.config.conn_max_idle_time' '"5m"'
assert_field_value 'redis config.dial_timeout' '.vector_store.config.dial_timeout' '"5s"'
assert_field_value 'redis config.read_timeout' '.vector_store.config.read_timeout' '"3s"'
assert_field_value 'redis config.write_timeout' '.vector_store.config.write_timeout' '"3s"'
assert_field_value 'redis config.context_timeout' '.vector_store.config.context_timeout' '"10s"'
# Qdrant with all fields
cat > "$TMPDIR/values-vs-qdrant.yaml" << 'VALS'
image:
tag: v1.0.0
vectorStore:
enabled: true
type: qdrant
qdrant:
external:
enabled: true
host: "qdrant.example.com"
port: 6334
apiKey: "qdrant-api-key"
useTls: true
VALS
render_config "$TMPDIR/values-vs-qdrant.yaml"
assert_field_value 'vector_store.type (qdrant)' '.vector_store.type' '"qdrant"'
assert_field_value 'qdrant config.host' '.vector_store.config.host' '"qdrant.example.com"'
assert_field_value 'qdrant config.port' '.vector_store.config.port' '6334'
assert_field_value 'qdrant config.api_key' '.vector_store.config.api_key' '"qdrant-api-key"'
assert_field_value 'qdrant config.use_tls' '.vector_store.config.use_tls' 'true'
# Pinecone with all fields
cat > "$TMPDIR/values-vs-pinecone.yaml" << 'VALS'
image:
tag: v1.0.0
vectorStore:
enabled: true
type: pinecone
pinecone:
external:
enabled: true
apiKey: "pinecone-api-key"
indexHost: "my-index.svc.us-east1.pinecone.io"
VALS
render_config "$TMPDIR/values-vs-pinecone.yaml"
assert_field_value 'vector_store.type (pinecone)' '.vector_store.type' '"pinecone"'
assert_field_value 'pinecone config.api_key' '.vector_store.config.api_key' '"pinecone-api-key"'
assert_field_value 'pinecone config.index_host' '.vector_store.config.index_host' '"my-index.svc.us-east1.pinecone.io"'
###############################################################################
# 10. Config Store & Logs Store (sqlite + postgres)
###############################################################################
echo ""
echo -e "${CYAN}💾 10/10 - Config Store & Logs Store${NC}"
echo "--------------------------------------"
# SQLite stores
cat > "$TMPDIR/values-stores-sqlite.yaml" << 'VALS'
image:
tag: v1.0.0
storage:
mode: sqlite
configStore:
enabled: true
logsStore:
enabled: true
VALS
render_config "$TMPDIR/values-stores-sqlite.yaml"
assert_field_value 'config_store.type (sqlite)' '.config_store.type' '"sqlite"'
assert_field_value 'config_store.enabled' '.config_store.enabled' 'true'
assert_field 'config_store.config.path' '.config_store.config.path'
assert_field_value 'logs_store.type (sqlite)' '.logs_store.type' '"sqlite"'
assert_field_value 'logs_store.enabled' '.logs_store.enabled' 'true'
assert_field 'logs_store.config.path' '.logs_store.config.path'
# Postgres stores
cat > "$TMPDIR/values-stores-pg.yaml" << 'VALS'
image:
tag: v1.0.0
storage:
mode: postgres
configStore:
enabled: true
maxIdleConns: 10
maxOpenConns: 100
logsStore:
enabled: true
maxIdleConns: 5
maxOpenConns: 50
postgresql:
enabled: true
auth:
username: bifrost
password: testpass
database: bifrost
VALS
render_config "$TMPDIR/values-stores-pg.yaml"
assert_field_value 'config_store.type (postgres)' '.config_store.type' '"postgres"'
assert_field 'config_store.config.host' '.config_store.config.host'
assert_field 'config_store.config.port' '.config_store.config.port'
assert_field 'config_store.config.user' '.config_store.config.user'
assert_field 'config_store.config.password' '.config_store.config.password'
assert_field 'config_store.config.db_name' '.config_store.config.db_name'
assert_field 'config_store.config.ssl_mode' '.config_store.config.ssl_mode'
assert_field_value 'config_store.config.max_idle_conns' '.config_store.config.max_idle_conns' '10'
assert_field_value 'config_store.config.max_open_conns' '.config_store.config.max_open_conns' '100'
assert_field_value 'logs_store.type (postgres)' '.logs_store.type' '"postgres"'
assert_field_value 'logs_store.config.max_idle_conns' '.logs_store.config.max_idle_conns' '5'
assert_field_value 'logs_store.config.max_open_conns' '.logs_store.config.max_open_conns' '50'
###############################################################################
# Object Storage (logsStore.objectStorage)
###############################################################################
# S3 with inline credentials — exercises camelCase → snake_case mapping in _helpers.tpl
cat > "$TMPDIR/values-objstore-s3.yaml" << 'VALS'
image:
tag: v1.0.0
storage:
mode: sqlite
configStore:
enabled: true
logsStore:
enabled: true
objectStorage:
enabled: true
type: s3
bucket: "bifrost-logs"
prefix: "prod"
compress: true
region: "us-east-1"
endpoint: "https://minio.internal:9000"
accessKeyId: "AKIA..."
secretAccessKey: "secret"
roleArn: "arn:aws:iam::123:role/bifrost"
forcePathStyle: true
VALS
render_config "$TMPDIR/values-objstore-s3.yaml"
assert_field_value 'logs_store.object_storage.type (s3)' '.logs_store.object_storage.type' '"s3"'
assert_field_value 'logs_store.object_storage.bucket' '.logs_store.object_storage.bucket' '"bifrost-logs"'
assert_field_value 'logs_store.object_storage.prefix' '.logs_store.object_storage.prefix' '"prod"'
assert_field_value 'logs_store.object_storage.compress' '.logs_store.object_storage.compress' 'true'
assert_field_value 'logs_store.object_storage.region' '.logs_store.object_storage.region' '"us-east-1"'
assert_field_value 'logs_store.object_storage.endpoint' '.logs_store.object_storage.endpoint' '"https://minio.internal:9000"'
assert_field_value 'logs_store.object_storage.access_key_id' '.logs_store.object_storage.access_key_id' '"AKIA..."'
assert_field_value 'logs_store.object_storage.secret_access_key' '.logs_store.object_storage.secret_access_key' '"secret"'
assert_field_value 'logs_store.object_storage.role_arn' '.logs_store.object_storage.role_arn' '"arn:aws:iam::123:role/bifrost"'
assert_field_value 'logs_store.object_storage.force_path_style' '.logs_store.object_storage.force_path_style' 'true'
# S3 with existingSecret — exercises env.BIFROST_OBJECT_STORAGE_* substitution path
cat > "$TMPDIR/values-objstore-s3-secret.yaml" << 'VALS'
image:
tag: v1.0.0
storage:
mode: sqlite
configStore:
enabled: true
logsStore:
enabled: true
objectStorage:
enabled: true
type: s3
bucket: "bifrost-logs"
existingSecret: "bifrost-os-creds"
accessKeyIdKey: "access-key-id"
secretAccessKeyKey: "secret-access-key"
sessionTokenKey: "session-token"
roleArnKey: "role-arn"
VALS
render_config "$TMPDIR/values-objstore-s3-secret.yaml"
assert_field_value 'logs_store.object_storage.access_key_id (env)' '.logs_store.object_storage.access_key_id' '"env.BIFROST_OBJECT_STORAGE_ACCESS_KEY_ID"'
assert_field_value 'logs_store.object_storage.secret_access_key (env)' '.logs_store.object_storage.secret_access_key' '"env.BIFROST_OBJECT_STORAGE_SECRET_ACCESS_KEY"'
assert_field_value 'logs_store.object_storage.session_token (env)' '.logs_store.object_storage.session_token' '"env.BIFROST_OBJECT_STORAGE_SESSION_TOKEN"'
assert_field_value 'logs_store.object_storage.role_arn (env)' '.logs_store.object_storage.role_arn' '"env.BIFROST_OBJECT_STORAGE_ROLE_ARN"'
# GCS — exercises project_id + credentials_json mapping
cat > "$TMPDIR/values-objstore-gcs.yaml" << 'VALS'
image:
tag: v1.0.0
storage:
mode: sqlite
configStore:
enabled: true
logsStore:
enabled: true
objectStorage:
enabled: true
type: gcs
bucket: "bifrost-gcs-bucket"
projectId: "my-gcp-project"
credentialsJson: "/etc/gcs/creds.json"
VALS
render_config "$TMPDIR/values-objstore-gcs.yaml"
assert_field_value 'logs_store.object_storage.type (gcs)' '.logs_store.object_storage.type' '"gcs"'
assert_field_value 'logs_store.object_storage.bucket (gcs)' '.logs_store.object_storage.bucket' '"bifrost-gcs-bucket"'
assert_field_value 'logs_store.object_storage.project_id' '.logs_store.object_storage.project_id' '"my-gcp-project"'
assert_field_value 'logs_store.object_storage.credentials_json' '.logs_store.object_storage.credentials_json' '"/etc/gcs/creds.json"'
###############################################################################
# Summary
###############################################################################
echo ""
echo "======================================================================"
echo "🏁 Config JSON Field 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 field validations failed. Please review the output above.${NC}"
exit 1
else
echo -e "${GREEN}✅ All config.json field validations passed!${NC}"
exit 0
fi