Improve basic example to showcase all resolver types

- Replace simple ENV-only example with diverse resolver showcase
- Add GCP Secret Manager, AWS Secrets Manager, Vault examples in main config
- Add JSON resolver example for loading service configs and feature flags
- Add Consul, K8S, and FILE resolvers for different config aspects
- Include example JSON files to show what JSON resolver references
- Update Go code to demonstrate accessing the various config types
- Move away from env-centric example to show full power upfront
This commit is contained in:
Jeffrey Paul 2025-07-21 16:21:33 +02:00
parent 81eddf2b38
commit e6db26d2c4

135
README.md
View File

@ -40,33 +40,49 @@ go get git.eeqj.de/sneak/smartconfig
```yaml ```yaml
# config.yaml # config.yaml
# Example with top-level values for easy access # Showcase the power of multiple resolver types
app_name: ${ENV:APP_NAME} app_name: ${ENV:APP_NAME}
port: ${ENV:PORT} # Becomes number 8080 if PORT="8080" version: ${FILE:/app/VERSION} # Read from file
debug_mode: ${ENV:DEBUG_MODE} # Becomes boolean true if DEBUG_MODE="true" port: ${ENV:PORT} # Number if PORT="8080"
cache_size_bytes: ${ENV:CACHE_SIZE} # Human-readable: "100MB", "1.5GB" debug_mode: ${ENV:DEBUG_MODE} # Boolean if "true"/"false"
# Nested configuration for organization # External service configuration from JSON config file
server: services: ${JSON:/etc/config/services.json:production} # Load entire object
host: localhost
port: ${ENV:SERVER_PORT}
name: "server-${ENV:INSTANCE_ID}" # Always a string due to mixed content
# API credentials from cloud secret managers
api_keys:
stripe: ${GCPSM:projects/myproject/secrets/stripe-api-key}
sendgrid: ${AWSSM:prod/sendgrid-api-key}
twilio: ${VAULT:secret/data/api:twilio_key}
datadog: ${ENV:DD_API_KEY} # Fallback to env for local dev
# Database configuration with mixed sources
database: database:
host: ${ENV:DB_HOST} host: ${CONSUL:service/postgres/address}
port: ${ENV:DB_PORT} port: ${CONSUL:service/postgres/port}
name: ${ENV:DB_NAME}
username: ${ENV:DB_USER} username: ${ENV:DB_USER}
password: ${VAULT:secret/data/myapp:db_password} password: ${GCPSM:projects/myproject/secrets/db-password}
# SSL configuration from multiple sources
ssl:
enabled: ${ENV:DB_SSL_ENABLED}
ca_cert: ${FILE:/etc/ssl/db-ca.crt}
client_cert: ${K8SS:default/db-certs:client.crt}
features: # Feature flags from JSON file
cache_enabled: ${ENV:CACHE_ENABLED} # Boolean if "true"/"false" features: ${JSON:/etc/config/features.json:${ENV:ENVIRONMENT}}
max_connections: ${ENV:MAX_CONN} # Number if numeric string
debug: ${ENV:FEATURES_DEBUG} # Server configuration
server:
listen: "0.0.0.0:${ENV:PORT}"
workers: ${EXEC:nproc} # Dynamic based on CPU cores
hostname: ${EXEC:hostname -f}
env: env:
# These will be exported as environment variables # Export these as environment variables for child processes
API_TOKEN: ${AWSSM:api-token} DATABASE_URL: "postgres://${ENV:DB_USER}:${GCPSM:projects/myproject/secrets/db-password}@${CONSUL:service/postgres/address}:5432/${ENV:DB_NAME}"
SLACK_WEBHOOK: ${FILE:/etc/secrets/slack_webhook.txt} NEW_RELIC_LICENSE: ${AWSSM:monitoring/newrelic-license}
``` ```
### Go Code ### Go Code
@ -77,6 +93,7 @@ package main
import ( import (
"fmt" "fmt"
"log" "log"
"os"
"git.eeqj.de/sneak/smartconfig" "git.eeqj.de/sneak/smartconfig"
) )
@ -88,29 +105,77 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
// Access typed values (top-level keys only) // Access typed values
port, err := config.GetInt("port") appName, _ := config.GetString("app_name")
if err != nil { port, _ := config.GetInt("port")
log.Fatal(err) debugMode, _ := config.GetBool("debug_mode")
}
// For nested values, navigate manually fmt.Printf("Starting %s on port %d (debug: %v)\n", appName, port, debugMode)
// Access nested values
data := config.Data() data := config.Data()
var debugMode bool
if features, ok := data["features"].(map[string]interface{}); ok { // API keys configuration
if debug, ok := features["debug"].(bool); ok { if apiKeys, ok := data["api_keys"].(map[string]interface{}); ok {
debugMode = debug if stripe, ok := apiKeys["stripe"].(string); ok {
fmt.Printf("Stripe API key loaded: %s...\n", stripe[:8])
} }
} }
// Human-readable byte sizes (top-level) // Database configuration
cacheSize, err := config.GetBytes("cache_size_bytes") if db, ok := data["database"].(map[string]interface{}); ok {
if err != nil { host, _ := db["host"].(string)
log.Fatal(err) port, _ := db["port"].(int)
fmt.Printf("Database: %s:%d\n", host, port)
} }
fmt.Printf("Server running on port %d (debug: %v)\n", port, debugMode) // Check loaded services from JSON file
fmt.Printf("Cache size: %d bytes\n", cacheSize) if services, ok := data["services"].(map[string]interface{}); ok {
fmt.Printf("Loaded %d services from JSON config\n", len(services))
}
// Environment variables are now available
fmt.Printf("DATABASE_URL env var: %s\n", os.Getenv("DATABASE_URL"))
}
```
Example JSON files referenced above:
```json
// /etc/config/services.json
{
"production": {
"api": {
"endpoint": "https://api.example.com",
"timeout": 30,
"retries": 3
},
"cache": {
"provider": "redis",
"ttl": 3600
}
},
"staging": {
"api": {
"endpoint": "https://api-staging.example.com",
"timeout": 60,
"retries": 5
}
}
}
// /etc/config/features.json
{
"production": {
"new_ui": false,
"rate_limiting": true,
"analytics": true
},
"staging": {
"new_ui": true,
"rate_limiting": true,
"analytics": false
}
} }
``` ```