perbaikan config

This commit is contained in:
2025-08-17 07:10:56 +07:00
parent 229782dad8
commit a7d6005649
10 changed files with 658 additions and 545 deletions

View File

@@ -4,12 +4,15 @@ import (
"log"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
Server ServerConfig
Database DatabaseConfig
Keycloak KeycloakConfig
Server ServerConfig
Databases map[string]DatabaseConfig
ReadReplicas map[string][]DatabaseConfig // For read replicas
Keycloak KeycloakConfig
}
type ServerConfig struct {
@@ -18,12 +21,20 @@ type ServerConfig struct {
}
type DatabaseConfig struct {
Host string
Port int
Username string
Password string
Database string
Schema string
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 {
@@ -39,14 +50,8 @@ func LoadConfig() *Config {
Port: getEnvAsInt("PORT", 8080),
Mode: getEnv("GIN_MODE", "debug"),
},
Database: DatabaseConfig{
Host: getEnv("BLUEPRINT_DB_HOST", "localhost"),
Port: getEnvAsInt("BLUEPRINT_DB_PORT", 5432),
Username: getEnv("BLUEPRINT_DB_USERNAME", "postgres"),
Password: getEnv("BLUEPRINT_DB_PASSWORD", "postgres"),
Database: getEnv("BLUEPRINT_DB_DATABASE", "api_service"),
Schema: getEnv("BLUEPRINT_DB_SCHEMA", "public"),
},
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"),
@@ -55,9 +60,249 @@ func LoadConfig() *Config {
},
}
// Load database configurations
config.loadDatabaseConfigs()
// Load read replica configurations
config.loadReadReplicaConfigs()
return config
}
func (c *Config) loadDatabaseConfigs() {
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
}
}
// Parse DB_ prefixed variables
if strings.HasPrefix(key, "DB_") && !strings.Contains(key, "_REPLICA_") {
segments := strings.Split(key, "_")
if len(segments) >= 3 {
dbName := strings.ToLower(segments[1])
property := strings.ToLower(strings.Join(segments[2:], "_"))
if dbConfigs[dbName] == nil {
dbConfigs[dbName] = make(map[string]string)
}
dbConfigs[dbName][property] = value
}
}
// Parse legacy format (for backward compatibility)
if strings.HasPrefix(key, "BLUEPRINT_DB_") {
if dbConfigs["primary"] == nil {
dbConfigs["primary"] = make(map[string]string)
}
property := strings.ToLower(strings.TrimPrefix(key, "BLUEPRINT_DB_"))
dbConfigs["primary"][property] = value
}
}
// Create DatabaseConfig from parsed configurations
for name, config := range dbConfigs {
// Skip empty configurations or system configurations
if name == "" || strings.Contains(name, "chrome_crashpad_pipe") {
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
}
// Handle legacy format
if name == "primary" && dbConfig.Type == "postgres" && dbConfig.Host == "localhost" {
dbConfig.Host = getEnv("BLUEPRINT_DB_HOST", "localhost")
dbConfig.Port = getEnvAsInt("BLUEPRINT_DB_PORT", 5432)
dbConfig.Username = getEnv("BLUEPRINT_DB_USERNAME", "postgres")
dbConfig.Password = getEnv("BLUEPRINT_DB_PASSWORD", "postgres")
dbConfig.Database = getEnv("BLUEPRINT_DB_DATABASE", "api_service")
dbConfig.Schema = getEnv("BLUEPRINT_DB_SCHEMA", "public")
}
c.Databases[name] = dbConfig
}
// Add specific databases from .env if not already parsed
c.addSpecificDatabase("db", "postgres")
c.addSpecificDatabase("simrs", "postgres")
c.addSpecificDatabase("antrian", "mysql")
c.addSpecificDatabase("satudata", "postgres")
c.addSpecificDatabase("mongodb_dev", "mongodb")
}
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
@@ -82,17 +327,23 @@ func getEnvAsBool(key string, defaultValue bool) bool {
}
func (c *Config) Validate() error {
if c.Database.Host == "" {
log.Fatal("Database host is required")
if len(c.Databases) == 0 {
log.Fatal("At least one database configuration is required")
}
if c.Database.Username == "" {
log.Fatal("Database username is required")
}
if c.Database.Password == "" {
log.Fatal("Database password is required")
}
if c.Database.Database == "" {
log.Fatal("Database name 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