360 lines
12 KiB
Go
360 lines
12 KiB
Go
package config
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig
|
|
Databases map[string]DatabaseConfig
|
|
ReadReplicas map[string][]DatabaseConfig // For read replicas
|
|
Keycloak KeycloakConfig
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Port int
|
|
Mode string
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Name string
|
|
Type string // postgres, mysql, sqlserver, sqlite, mongodb
|
|
Host string
|
|
Port int
|
|
Username string
|
|
Password string
|
|
Database string
|
|
Schema string
|
|
SSLMode string
|
|
Path string // For SQLite
|
|
Options string // Additional connection options
|
|
MaxOpenConns int // Max open connections
|
|
MaxIdleConns int // Max idle connections
|
|
ConnMaxLifetime time.Duration // Connection max lifetime
|
|
}
|
|
|
|
type KeycloakConfig struct {
|
|
Issuer string
|
|
Audience string
|
|
JwksURL string
|
|
Enabled bool
|
|
}
|
|
|
|
func LoadConfig() *Config {
|
|
config := &Config{
|
|
Server: ServerConfig{
|
|
Port: getEnvAsInt("PORT", 8080),
|
|
Mode: getEnv("GIN_MODE", "debug"),
|
|
},
|
|
Databases: make(map[string]DatabaseConfig),
|
|
ReadReplicas: make(map[string][]DatabaseConfig),
|
|
Keycloak: KeycloakConfig{
|
|
Issuer: getEnv("KEYCLOAK_ISSUER", "https://keycloak.example.com/auth/realms/yourrealm"),
|
|
Audience: getEnv("KEYCLOAK_AUDIENCE", "your-client-id"),
|
|
JwksURL: getEnv("KEYCLOAK_JWKS_URL", "https://keycloak.example.com/auth/realms/yourrealm/protocol/openid-connect/certs"),
|
|
Enabled: getEnvAsBool("KEYCLOAK_ENABLED", true),
|
|
},
|
|
}
|
|
|
|
// Load database configurations
|
|
config.loadDatabaseConfigs()
|
|
|
|
// Load read replica configurations
|
|
config.loadReadReplicaConfigs()
|
|
|
|
return config
|
|
}
|
|
|
|
func (c *Config) loadDatabaseConfigs() {
|
|
// Simplified approach: Directly load from environment variables
|
|
// This ensures we get the exact values specified in .env
|
|
|
|
// Primary database configuration
|
|
c.Databases["primary"] = DatabaseConfig{
|
|
Name: "primary",
|
|
Type: getEnv("DB_CONNECTION", "postgres"),
|
|
Host: getEnv("DB_HOST", "localhost"),
|
|
Port: getEnvAsInt("DB_PORT", 5432),
|
|
Username: getEnv("DB_USERNAME", ""),
|
|
Password: getEnv("DB_PASSWORD", ""),
|
|
Database: getEnv("DB_DATABASE", "satu_db"),
|
|
Schema: getEnv("DB_SCHEMA", "public"),
|
|
SSLMode: getEnv("DB_SSLMODE", "disable"),
|
|
MaxOpenConns: getEnvAsInt("DB_MAX_OPEN_CONNS", 25),
|
|
MaxIdleConns: getEnvAsInt("DB_MAX_IDLE_CONNS", 25),
|
|
ConnMaxLifetime: parseDuration(getEnv("DB_CONN_MAX_LIFETIME", "5m")),
|
|
}
|
|
|
|
// SATUDATA database configuration
|
|
c.Databases["satudata"] = DatabaseConfig{
|
|
Name: "satudata",
|
|
Type: getEnv("SATUDATA_CONNECTION", "postgres"),
|
|
Host: getEnv("SATUDATA_HOST", "localhost"),
|
|
Port: getEnvAsInt("SATUDATA_PORT", 5432),
|
|
Username: getEnv("SATUDATA_USERNAME", ""),
|
|
Password: getEnv("SATUDATA_PASSWORD", ""),
|
|
Database: getEnv("SATUDATA_DATABASE", "satu_db"),
|
|
Schema: getEnv("SATUDATA_SCHEMA", "public"),
|
|
SSLMode: getEnv("SATUDATA_SSLMODE", "disable"),
|
|
MaxOpenConns: getEnvAsInt("SATUDATA_MAX_OPEN_CONNS", 25),
|
|
MaxIdleConns: getEnvAsInt("SATUDATA_MAX_IDLE_CONNS", 25),
|
|
ConnMaxLifetime: parseDuration(getEnv("SATUDATA_CONN_MAX_LIFETIME", "5m")),
|
|
}
|
|
|
|
// Legacy support for backward compatibility
|
|
envVars := os.Environ()
|
|
dbConfigs := make(map[string]map[string]string)
|
|
|
|
// Parse database configurations from environment variables
|
|
for _, envVar := range envVars {
|
|
parts := strings.SplitN(envVar, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := parts[0]
|
|
value := parts[1]
|
|
|
|
// Parse specific database configurations
|
|
if strings.HasSuffix(key, "_CONNECTION") || strings.HasSuffix(key, "_HOST") ||
|
|
strings.HasSuffix(key, "_DATABASE") || strings.HasSuffix(key, "_USERNAME") ||
|
|
strings.HasSuffix(key, "_PASSWORD") || strings.HasSuffix(key, "_PORT") ||
|
|
strings.HasSuffix(key, "_NAME") {
|
|
|
|
segments := strings.Split(key, "_")
|
|
if len(segments) >= 2 {
|
|
dbName := strings.ToLower(strings.Join(segments[:len(segments)-1], "_"))
|
|
property := strings.ToLower(segments[len(segments)-1])
|
|
|
|
if dbConfigs[dbName] == nil {
|
|
dbConfigs[dbName] = make(map[string]string)
|
|
}
|
|
dbConfigs[dbName][property] = value
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create DatabaseConfig from parsed configurations for additional databases
|
|
for name, config := range dbConfigs {
|
|
// Skip empty configurations or system configurations
|
|
if name == "" || strings.Contains(name, "chrome_crashpad_pipe") || name == "primary" || name == "satudata" {
|
|
continue
|
|
}
|
|
|
|
dbConfig := DatabaseConfig{
|
|
Name: name,
|
|
Type: getEnvFromMap(config, "connection", getEnvFromMap(config, "type", "postgres")),
|
|
Host: getEnvFromMap(config, "host", "localhost"),
|
|
Port: getEnvAsIntFromMap(config, "port", 5432),
|
|
Username: getEnvFromMap(config, "username", ""),
|
|
Password: getEnvFromMap(config, "password", ""),
|
|
Database: getEnvFromMap(config, "database", getEnvFromMap(config, "name", name)),
|
|
Schema: getEnvFromMap(config, "schema", "public"),
|
|
SSLMode: getEnvFromMap(config, "sslmode", "disable"),
|
|
Path: getEnvFromMap(config, "path", ""),
|
|
Options: getEnvFromMap(config, "options", ""),
|
|
MaxOpenConns: getEnvAsIntFromMap(config, "max_open_conns", 25),
|
|
MaxIdleConns: getEnvAsIntFromMap(config, "max_idle_conns", 25),
|
|
ConnMaxLifetime: parseDuration(getEnvFromMap(config, "conn_max_lifetime", "5m")),
|
|
}
|
|
|
|
// Skip if username is empty and it's not a system config
|
|
if dbConfig.Username == "" && !strings.HasPrefix(name, "chrome") {
|
|
continue
|
|
}
|
|
|
|
c.Databases[name] = dbConfig
|
|
}
|
|
}
|
|
|
|
func (c *Config) loadReadReplicaConfigs() {
|
|
envVars := os.Environ()
|
|
|
|
for _, envVar := range envVars {
|
|
parts := strings.SplitN(envVar, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := parts[0]
|
|
value := parts[1]
|
|
|
|
// Parse read replica configurations (format: [DBNAME]_REPLICA_[INDEX]_[PROPERTY])
|
|
if strings.Contains(key, "_REPLICA_") {
|
|
segments := strings.Split(key, "_")
|
|
if len(segments) >= 5 && strings.ToUpper(segments[2]) == "REPLICA" {
|
|
dbName := strings.ToLower(segments[1])
|
|
replicaIndex := segments[3]
|
|
property := strings.ToLower(strings.Join(segments[4:], "_"))
|
|
|
|
replicaKey := dbName + "_replica_" + replicaIndex
|
|
|
|
if c.ReadReplicas[dbName] == nil {
|
|
c.ReadReplicas[dbName] = []DatabaseConfig{}
|
|
}
|
|
|
|
// Find or create replica config
|
|
var replicaConfig *DatabaseConfig
|
|
for i := range c.ReadReplicas[dbName] {
|
|
if c.ReadReplicas[dbName][i].Name == replicaKey {
|
|
replicaConfig = &c.ReadReplicas[dbName][i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if replicaConfig == nil {
|
|
// Create new replica config
|
|
newConfig := DatabaseConfig{
|
|
Name: replicaKey,
|
|
Type: c.Databases[dbName].Type,
|
|
Host: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_HOST", c.Databases[dbName].Host),
|
|
Port: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_PORT", c.Databases[dbName].Port),
|
|
Username: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_USERNAME", c.Databases[dbName].Username),
|
|
Password: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_PASSWORD", c.Databases[dbName].Password),
|
|
Database: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_DATABASE", c.Databases[dbName].Database),
|
|
Schema: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_SCHEMA", c.Databases[dbName].Schema),
|
|
SSLMode: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_SSLMODE", c.Databases[dbName].SSLMode),
|
|
MaxOpenConns: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_MAX_OPEN_CONNS", c.Databases[dbName].MaxOpenConns),
|
|
MaxIdleConns: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_MAX_IDLE_CONNS", c.Databases[dbName].MaxIdleConns),
|
|
ConnMaxLifetime: parseDuration(getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_CONN_MAX_LIFETIME", "5m")),
|
|
}
|
|
c.ReadReplicas[dbName] = append(c.ReadReplicas[dbName], newConfig)
|
|
replicaConfig = &c.ReadReplicas[dbName][len(c.ReadReplicas[dbName])-1]
|
|
}
|
|
|
|
// Update the specific replica
|
|
switch property {
|
|
case "host":
|
|
replicaConfig.Host = value
|
|
case "port":
|
|
replicaConfig.Port = getEnvAsInt(key, 5432)
|
|
case "username":
|
|
replicaConfig.Username = value
|
|
case "password":
|
|
replicaConfig.Password = value
|
|
case "database":
|
|
replicaConfig.Database = value
|
|
case "schema":
|
|
replicaConfig.Schema = value
|
|
case "sslmode":
|
|
replicaConfig.SSLMode = value
|
|
case "max_open_conns":
|
|
replicaConfig.MaxOpenConns = getEnvAsInt(key, 25)
|
|
case "max_idle_conns":
|
|
replicaConfig.MaxIdleConns = getEnvAsInt(key, 25)
|
|
case "conn_max_lifetime":
|
|
replicaConfig.ConnMaxLifetime = parseDuration(value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Config) addSpecificDatabase(prefix, defaultType string) {
|
|
connection := getEnv(strings.ToUpper(prefix)+"_CONNECTION", defaultType)
|
|
host := getEnv(strings.ToUpper(prefix)+"_HOST", "")
|
|
if host != "" {
|
|
dbConfig := DatabaseConfig{
|
|
Name: prefix,
|
|
Type: connection,
|
|
Host: host,
|
|
Port: getEnvAsInt(strings.ToUpper(prefix)+"_PORT", 5432),
|
|
Username: getEnv(strings.ToUpper(prefix)+"_USERNAME", ""),
|
|
Password: getEnv(strings.ToUpper(prefix)+"_PASSWORD", ""),
|
|
Database: getEnv(strings.ToUpper(prefix)+"_DATABASE", getEnv(strings.ToUpper(prefix)+"_NAME", prefix)),
|
|
Schema: getEnv(strings.ToUpper(prefix)+"_SCHEMA", "public"),
|
|
SSLMode: getEnv(strings.ToUpper(prefix)+"_SSLMODE", "disable"),
|
|
MaxOpenConns: getEnvAsInt(strings.ToUpper(prefix)+"_MAX_OPEN_CONNS", 25),
|
|
MaxIdleConns: getEnvAsInt(strings.ToUpper(prefix)+"_MAX_IDLE_CONNS", 25),
|
|
ConnMaxLifetime: parseDuration(getEnv(strings.ToUpper(prefix)+"_CONN_MAX_LIFETIME", "5m")),
|
|
}
|
|
c.Databases[prefix] = dbConfig
|
|
}
|
|
}
|
|
|
|
func getEnvFromMap(config map[string]string, key, defaultValue string) string {
|
|
if value, exists := config[key]; exists {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvAsIntFromMap(config map[string]string, key string, defaultValue int) int {
|
|
if value, exists := config[key]; exists {
|
|
if intValue, err := strconv.Atoi(value); err == nil {
|
|
return intValue
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func parseDuration(durationStr string) time.Duration {
|
|
if duration, err := time.ParseDuration(durationStr); err == nil {
|
|
return duration
|
|
}
|
|
return 5 * time.Minute
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvAsInt(key string, defaultValue int) int {
|
|
valueStr := getEnv(key, "")
|
|
if value, err := strconv.Atoi(valueStr); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvAsBool(key string, defaultValue bool) bool {
|
|
valueStr := getEnv(key, "")
|
|
if value, err := strconv.ParseBool(valueStr); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
if len(c.Databases) == 0 {
|
|
log.Fatal("At least one database configuration is required")
|
|
}
|
|
|
|
for name, db := range c.Databases {
|
|
if db.Host == "" {
|
|
log.Fatalf("Database host is required for %s", name)
|
|
}
|
|
if db.Username == "" {
|
|
log.Fatalf("Database username is required for %s", name)
|
|
}
|
|
if db.Password == "" {
|
|
log.Fatalf("Database password is required for %s", name)
|
|
}
|
|
if db.Database == "" {
|
|
log.Fatalf("Database name is required for %s", name)
|
|
}
|
|
}
|
|
|
|
// Validate Keycloak configuration if enabled
|
|
if c.Keycloak.Enabled {
|
|
if c.Keycloak.Issuer == "" {
|
|
log.Fatal("Keycloak issuer is required when Keycloak is enabled")
|
|
}
|
|
if c.Keycloak.Audience == "" {
|
|
log.Fatal("Keycloak audience is required when Keycloak is enabled")
|
|
}
|
|
if c.Keycloak.JwksURL == "" {
|
|
log.Fatal("Keycloak JWKS URL is required when Keycloak is enabled")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|