1300 lines
55 KiB
Bash
Executable File
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 |